-
Notifications
You must be signed in to change notification settings - Fork 1
/
AbstractSQLCompiler.coffee
148 lines (137 loc) · 5.36 KB
/
AbstractSQLCompiler.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
((root, factory) ->
if typeof define is 'function' and define.amd
# AMD. Register as an anonymous module.
define([
'@resin/abstract-sql-compiler/AbstractSQLOptimiser'
'@resin/abstract-sql-compiler/AbstractSQLRules2SQL'
'@resin/sbvr-types'
'lodash'
'bluebird'
], factory)
else if typeof exports is 'object'
# Node. Does not work with strict CommonJS, but
# only CommonJS-like enviroments that support module.exports,
# like Node.
module.exports = factory(
require('./AbstractSQLOptimiser')
require('./AbstractSQLRules2SQL')
require('@resin/sbvr-types')
require('lodash')
require('bluebird')
)
else
# Browser globals
root.AbstractSQLCompiler = factory(root.AbstractSQLOptimiser, root.AbstractSQLRules2SQL, root.sbvrTypes, root._, root.Promise)
) this, ({ AbstractSQLOptimiser }, { AbstractSQLRules2SQL }, sbvrTypes, _, Promise) ->
validateTypes = _.mapValues sbvrTypes, ({ validate }) ->
if validate?
Promise.promisify(validate)
dataTypeValidate = (value, field, callback) ->
# In case one of the validation types throws an error.
{ dataType, required } = field
if value == null
if required
Promise.rejected('cannot be null')
else
Promise.fulfilled(null)
else if validateTypes[dataType]?
validateTypes[dataType](value, required)
else
Promise.rejected('is an unsupported type: ' + dataType)
dataTypeGen = (engine, dataType, necessity, index = '', defaultValue) ->
necessity = if necessity then ' NOT NULL' else ' NULL'
defaultValue = if defaultValue then " DEFAULT #{defaultValue}"
if index != ''
index = ' ' + index
dbType = sbvrTypes[dataType]?.types?[engine]
if dbType?
if _.isFunction(dbType)
return dbType(necessity, index)
defaultValue ?= ''
return dbType + defaultValue + necessity + index
else
throw new Error("Unknown data type '#{dataType}' for engine: #{engine}")
compileRule = do ->
optimiser = AbstractSQLOptimiser.createInstance()
compiler = AbstractSQLRules2SQL.createInstance()
return (abstractSQL, engine) ->
abstractSQL = optimiser.match(abstractSQL, 'Process')
compiler.engine = engine
return compiler.match(abstractSQL, 'Process')
compileSchema = (sqlModel, engine, ifNotExists) ->
ifNotExists = if ifNotExists then 'IF NOT EXISTS ' else ''
hasDependants = {}
schemaDependencyMap = {}
for own resourceName, table of sqlModel.tables when !_.isString(table)
foreignKeys = []
depends = []
dropSQL = 'DROP TABLE "' + table.name + '";'
createSQL = 'CREATE TABLE ' + ifNotExists + '"' + table.name + '" (\n\t'
for { dataType, fieldName, required, index, references, defaultValue } in table.fields
createSQL += '"' + fieldName + '" ' + dataTypeGen(engine, dataType, required, index, defaultValue) + '\n,\t'
if dataType in [ 'ForeignKey', 'ConceptType' ]
foreignKeys.push({ fieldName, references })
depends.push(references.tableName)
hasDependants[references.tableName] = true
for foreignKey in foreignKeys
createSQL += 'FOREIGN KEY ("' + foreignKey.fieldName + '") REFERENCES "' + foreignKey.references.tableName + '" ("' + foreignKey.references.fieldName + '")' + '\n,\t'
for index in table.indexes
createSQL += index.type + '("' + index.fields.join('", "') + '")\n,\t'
createSQL = createSQL[0...-2] + ');'
schemaDependencyMap[table.name] =
resourceName: resourceName
primitive: table.primitive
createSQL: createSQL
dropSQL: dropSQL
depends: depends
createSchemaStatements = []
dropSchemaStatements = []
tableNames = []
while tableNames.length != (tableNames = Object.keys(schemaDependencyMap)).length && tableNames.length > 0
for tableName in tableNames
schemaInfo = schemaDependencyMap[tableName]
unsolvedDependency = false
for dependency in schemaInfo.depends when dependency != schemaInfo.resourceName # Self-dependencies are ok.
if schemaDependencyMap.hasOwnProperty(dependency)
unsolvedDependency = true
break
if unsolvedDependency == false
if sqlModel.tables[schemaInfo.resourceName].exists = (schemaInfo.primitive == false || hasDependants[tableName]?)
if schemaInfo.primitive != false
console.warn("We're adding a primitive table??", schemaInfo.resourceName)
createSchemaStatements.push(schemaInfo.createSQL)
dropSchemaStatements.push(schemaInfo.dropSQL)
delete schemaDependencyMap[tableName]
if schemaDependencyMap.length > 0
console.error('Failed to resolve all schema dependencies', schemaDependencyMap)
throw new Error('Failed to resolve all schema dependencies')
dropSchemaStatements = dropSchemaStatements.reverse()
ruleStatements = []
try
for rule in sqlModel.rules
ruleBody = _.find(rule, 0: 'Body')[1]
ruleSE = _.find(rule, 0: 'StructuredEnglish')[1]
ruleSQL = compileRule(ruleBody, engine)
ruleStatements.push(
structuredEnglish: ruleSE
sql: ruleSQL
)
catch e
console.error('Failed to compile the rule', JSON.stringify(rule, null, '\t'))
console.error(e, e.stack)
throw e
return {
tables: sqlModel.tables
createSchema: createSchemaStatements
dropSchema: dropSchemaStatements
rules: ruleStatements
}
module.exports =
_.mapValues
postgres: true
mysql: true
websql: false
(ifNotExists, engine) ->
compileSchema: _.partial(compileSchema, _, engine, ifNotExists)
compileRule: _.partial(compileRule, _, engine)
dataTypeValidate: dataTypeValidate