@cap-js/sqlite 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,233 @@
1
+ const cds = require('@sap/cds')
2
+ const { getDBTable } = require('@sap/cds/libx/_runtime/common/utils/resolveView')
3
+ const { compareJson } = require('@sap/cds/libx/_runtime/cds-services/services/utils/compareJson')
4
+ const { target_name4 } = require('./utils')
5
+
6
+ const handledDeep = Symbol('handledDeep')
7
+
8
+ async function onDeep (req, next) {
9
+ const { query } = req
10
+ // REVISIT: req.target does not match the query.INSERT target for path insert
11
+ // cds.inferred(query)
12
+ // const target = query.sources[Object.keys(query.sources)[0]]
13
+ if (!this.model?.definitions[target_name4(req.query)]) {
14
+ return next()
15
+ }
16
+ const {target} = this.infer(query)
17
+ if (!hasDeep(query, target)) return next()
18
+ const beforeData = query.INSERT ? [] : await this.run(getExpandForDeep(query, target, true))
19
+ const queries = getDeepQueries(query, beforeData, target)
20
+ const res = await Promise.all(queries.map (q => this.run(q)))
21
+ return res[0] ?? 0 // TODO what todo with multiple result responses?
22
+ }
23
+
24
+
25
+ const hasDeep = (query, target) => {
26
+ if (handledDeep in query) return
27
+ if (query.DELETE) {
28
+ for (let c in target?.compositions) return true
29
+ return false
30
+ }
31
+ const data = query.INSERT?.entries || (query.UPDATE?.data && [query.UPDATE.data]) || (query.UPDATE?.with && [query.UPDATE.with])
32
+ if (data) for (const c in target.compositions) {
33
+ for (const row of data) if (row[c] !== undefined) return true
34
+ }
35
+ }
36
+
37
+ // unofficial config!
38
+ const DEEP_DELETE_MAX_RECURSION_DEPTH =
39
+ (cds.env.features.recursion_depth && Number(cds.env.features.recursion_depth)) || 4 // we use 4 here as our test data has a max depth of 3
40
+
41
+ // IMPORTANT: Skip only if @cds.persistence.skip is `true` → e.g. this skips skipping targets marked with @cds.persistence.skip: 'if-unused'
42
+ const _hasPersistenceSkip = target => target?.["@cds.persistence.skip"] === true
43
+
44
+ const getColumnsFromDataOrKeys = (data, target) => {
45
+ if (Array.isArray(data)) {
46
+ // loop and get all columns from current level
47
+ const columns = new Set()
48
+ data.forEach(row =>
49
+ Object.keys(row || target.keys)
50
+ .filter(propName => !target.elements[propName]?.isAssociation)
51
+ .forEach(entry => {
52
+ columns.add(entry)
53
+ })
54
+ )
55
+ return Array.from(columns).map(c => ({ ref: [c] }))
56
+ } else {
57
+ // get all columns from current level
58
+ return Object.keys(data || target.keys)
59
+ .filter(propName => !target.elements[propName]?.isAssociation)
60
+ .map(c => ({ ref: [c] }))
61
+ }
62
+ }
63
+
64
+ const _calculateExpandColumns = (compositions, data, expandColumns = [], elementMap = new Map()) => {
65
+ for (const compName in compositions) {
66
+ let compositionData
67
+ if (data === null) {
68
+ compositionData = null
69
+ } else {
70
+ compositionData = data[compName]
71
+ }
72
+ // ignore not provided compositions as nothing happens with them (expect deep delete)
73
+ if (compositionData === undefined) {
74
+ // fill columns in case
75
+ continue
76
+ }
77
+
78
+ const composition = compositions[compName]
79
+
80
+ const fqn = composition.parent.name + ':' + composition.name
81
+ const seen = elementMap.get(fqn)
82
+ if (seen && seen >= DEEP_DELETE_MAX_RECURSION_DEPTH) {
83
+ // recursion -> abort
84
+ return
85
+ }
86
+
87
+ let expandColumn = expandColumns.find(expandColumn => expandColumn.ref[0] === composition.name)
88
+ if (!expandColumn) {
89
+ expandColumn = {
90
+ ref: [composition.name],
91
+ expand: getColumnsFromDataOrKeys(compositionData, composition._target)
92
+ }
93
+ if (expandColumns.length === 0) {
94
+ expandColumns.push(...getColumnsFromDataOrKeys(data, composition.parent))
95
+ }
96
+
97
+ expandColumns.push(expandColumn)
98
+ }
99
+
100
+ const targetCompositions = composition._target.compositions
101
+
102
+ // expand deep
103
+ if (targetCompositions) {
104
+ // Make a copy and do not share the same map among brother compositions
105
+ // as we're only interested in deep recursions, not wide recursions.
106
+ const newElementMap = new Map(elementMap)
107
+ newElementMap.set(fqn, (seen && seen + 1) || 1)
108
+
109
+ if (composition.is2many) {
110
+ // expandColumn.expand = getColumnsFromDataOrKeys(compositionData, composition._target)
111
+ if (compositionData === null || compositionData.length === 0) {
112
+ // deep delete, get all subitems until recursion depth
113
+ _calculateExpandColumns(targetCompositions, compositionData, expandColumn.expand, newElementMap)
114
+ return
115
+ }
116
+
117
+ for (const row of compositionData) {
118
+ _calculateExpandColumns(targetCompositions, row, expandColumn.expand, newElementMap)
119
+ }
120
+ } else {
121
+ // to one
122
+ _calculateExpandColumns(targetCompositions, compositionData, expandColumn.expand, newElementMap)
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ const getExpandForDeep = (query, target) => {
129
+ const from = query.DELETE?.from || query.UPDATE?.entity
130
+ const data = query.UPDATE?.data || null
131
+ const where = query.DELETE?.where || query.UPDATE?.where
132
+
133
+ const cqn = SELECT.from(from)
134
+ if (where) cqn.SELECT.where = where
135
+
136
+ const compositions = target.compositions || {}
137
+
138
+ const columns = []
139
+ _calculateExpandColumns(compositions, data, columns)
140
+ cqn.columns(columns)
141
+ return cqn
142
+ }
143
+
144
+ const getDeepQueries = (query, dbData, target) => {
145
+ let queryData
146
+ if (query.INSERT) {
147
+ queryData = query.INSERT.entries
148
+ }
149
+ if (query.DELETE) {
150
+ queryData = []
151
+ }
152
+ if (query.UPDATE) {
153
+ queryData = [query.UPDATE.data]
154
+ }
155
+
156
+ let diff = compareJson(queryData, dbData, target)
157
+ if (!Array.isArray(diff)) {
158
+ diff = [diff]
159
+ }
160
+
161
+ return _getDeepQueries(diff, target)
162
+ }
163
+
164
+ const _getDeepQueries = (diff, target) => {
165
+ const queries = []
166
+
167
+ for (const diffEntry of diff) {
168
+ if (diffEntry === undefined) continue
169
+ const subQueries = []
170
+
171
+ for (const prop in diffEntry) {
172
+ // handle deep operations
173
+
174
+ const propData = diffEntry[prop]
175
+
176
+ if (target.elements[prop] && _hasPersistenceSkip(target.elements[prop]._target)) {
177
+ delete diffEntry[prop]
178
+ } else if (propData && propData._op) {
179
+ // to-one can also contain deep, so call recursively
180
+ subQueries.push(..._getDeepQueries([propData], target.elements[prop]._target))
181
+ delete diffEntry[prop]
182
+ } else if (Array.isArray(propData)) {
183
+ // REVISIT can be arrayed too
184
+ propData.forEach(subEntry => {
185
+ if (subEntry._op) {
186
+ subQueries.push(..._getDeepQueries([subEntry], target.elements[prop]._target))
187
+ }
188
+ })
189
+ delete diffEntry[prop]
190
+ }
191
+ }
192
+
193
+ // handle current entity level
194
+ const op = diffEntry._op
195
+ delete diffEntry._op
196
+
197
+ if (diffEntry._old != null) {
198
+ delete diffEntry._old
199
+ }
200
+
201
+ // first calculate subqueriesand rm their properties, then build root query
202
+ if (op === 'create') {
203
+ queries.push(INSERT.into(getDBTable(target)).entries(diffEntry))
204
+ } else if (op === 'delete') {
205
+ queries.push(DELETE.from(getDBTable(target)).where(diffEntry))
206
+ } else if (op === 'update') {
207
+ // TODO do we need the where here?
208
+ const keys = target.keys
209
+ const cqn = UPDATE(getDBTable(target)).with(diffEntry)
210
+ for (const key in keys) {
211
+ if (!keys[key].isAssociation) {
212
+ cqn.where(key + '=', diffEntry[key])
213
+ }
214
+ delete diffEntry[key]
215
+ }
216
+ cqn.with(diffEntry)
217
+ queries.push(cqn)
218
+ }
219
+
220
+ queries.push(...subQueries)
221
+ }
222
+
223
+ queries.forEach(q => {
224
+ Object.defineProperty(q, handledDeep, { value: true })
225
+ })
226
+ return queries
227
+ }
228
+
229
+ module.exports = {
230
+ onDeep,
231
+ getDeepQueries,
232
+ getExpandForDeep
233
+ }
@@ -0,0 +1,146 @@
1
+ const StandardFunctions = {
2
+
3
+ // OData: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_CanonicalFunctions
4
+
5
+ // String and Collection Functions
6
+ // length : (x) => `length(${x})`,
7
+ search: (ref, arg) => {
8
+ if (!('val' in arg)) throw `SQLite only supports single value arguments for $search`
9
+ const refs = ref.list || [ref], {toString} = ref
10
+ return '(' + refs.map(ref2 => `ifnull(instr(lower(${toString(ref2)}),lower(${arg})),0)`).join(' or ') + ')'
11
+ },
12
+ concat : (...args) => args.join('||'),
13
+ contains : (...args) => `ifnull(instr(${args}),0)`,
14
+ count : (x) => `count(${x||'*'})`,
15
+ indexof : (x,y) => `instr(${x},${y}) - 1`, // sqlite instr is 1 indexed
16
+ startswith : (x,y) => `instr(${x},${y}) = 1`, // sqlite instr is 1 indexed
17
+ endswith : (x,y) => `instr(${x},${y}) = length(${x}) - length(${y}) +1`,
18
+ substring : (x,y,z) => ( z
19
+ ? `substr( ${x}, case when ${y} < 0 then length(${x}) + ${y} + 1 else ${y} + 1 end, ${z} )`
20
+ : `substr( ${x}, case when ${y} < 0 then length(${x}) + ${y} + 1 else ${y} + 1 end )`
21
+ ),
22
+
23
+ // String Functions
24
+ matchesPattern : (x,y) => `${x} regexp ${y})`,
25
+ tolower : (x) => `lower(${x})`,
26
+ toupper : (x) => `upper(${x})`,
27
+ // trim : (x) => `trim(${x})`,
28
+
29
+ // Arithmetic Functions
30
+ ceiling : (x) => `ceil(${x})`,
31
+ // floor : (x) => `floor(${x})`,
32
+ // round : (x) => `round(${x})`,
33
+
34
+ // Date and Time Functions
35
+ year : (x) => `cast( strftime('%Y',${x}) as Integer )`,
36
+ month : (x) => `cast( strftime('%m',${x}) as Integer )`,
37
+ day : (x) => `cast( strftime('%d',${x}) as Integer )`,
38
+ hour : (x) => `cast( strftime('%H',${x}) as Integer )`,
39
+ minute : (x) => `cast( strftime('%M',${x}) as Integer )`,
40
+ second : (x) => `cast( strftime('%S',${x}) as Integer )`,
41
+
42
+ fractionalseconds : (x) => `cast( strftime('%f',${x}) as Integer )`,
43
+
44
+ maxdatetime : ()=> '9999-12-31 23:59:59.999',
45
+ mindatetime : ()=> '0001-01-01 00:00:00.000',
46
+
47
+ // odata spec defines the date time offset type as a normal ISO time stamp
48
+ // Where the timezone can either be 'Z' (for UTC) or [+|-]xx:xx for the time offset
49
+ // sqlite understands this so by splitting the timezone from the actual date
50
+ // prefixing it with 1970 it allows sqlite to give back the number of seconds
51
+ // which can be divided by 60 back to minutes
52
+ totaloffsetminutes : (x) => `case
53
+ when substr(${x}, length(${x})) = 'z' then 0
54
+ else strftime('%s', '1970-01-01T00:00:00' || substr(${x}, length(${x}) - 5)) / 60
55
+ end`,
56
+
57
+ // odata spec defines the value format for totalseconds as a duration like: P12DT23H59M59.999999999999S
58
+ // P -> duration indicator
59
+ // D -> days, T -> Time seperator, H -> hours, M -> minutes, S -> fractional seconds
60
+ // By splitting the DT and calculating the seconds of the time separate from the day
61
+ // it possible to determine the full amount of seconds by adding them together as fractionals and multiplying
62
+ // the number of seconds in a day
63
+ // As sqlite is most accurate with juliandays it is better to do then then using actual second function
64
+ // while the odata specification states that the seconds has to be fractional which only julianday allows
65
+ totalseconds : (x) => `(
66
+ (
67
+ (
68
+ cast(substr(${x},2,instr(${x},'DT') - 2) as Integer)
69
+ ) + (
70
+ julianday(
71
+ '-4713-11-25T' ||
72
+ replace(
73
+ replace(
74
+ replace(
75
+ substr(${x},instr(${x},'DT') + 2),
76
+ 'H',':'
77
+ ),'M',':'
78
+ ),'S','Z'
79
+ )
80
+ ) - 0.5
81
+ )
82
+ ) * 86400
83
+ )`,
84
+ }
85
+
86
+
87
+ const HANAFunctions = {
88
+ // https://help.sap.com/docs/SAP_HANA_PLATFORM/4fe29514fd584807ac9f2a04f6754767/f12b86a6284c4aeeb449e57eb5dd3ebd.html
89
+
90
+ // Time functions
91
+ nano100_between : (x,y) => `(julianday(${y}) - julianday(${x})) * 864000000000`,
92
+ seconds_between : (x,y) => `(julianday(${y}) - julianday(${x})) * 86400`,
93
+ // Calculates the difference in full days using julian day
94
+ // Using the exact time of the day to determine whether 24 hours have passed or not to add the final day
95
+ // When just comparing the julianday values with each other there are leap seconds included
96
+ // Which on the day resolution are included as the individual days therefor ignoring them to match HANA
97
+ days_between : (x,y) => `(
98
+ cast ( julianday(${y}) as Integer ) - cast ( julianday(${x}) as Integer )
99
+ ) + (
100
+ case
101
+ when ( julianday(${y}) < julianday(${x}) ) then
102
+ (cast( strftime('%H%M%S%f', ${y}) as Integer ) < cast( strftime('%H%M%S%f', ${x}) as Integer ))
103
+ else
104
+ (cast( strftime('%H%M%S%f', ${y}) as Integer ) > cast( strftime('%H%M%S%f', ${x}) as Integer )) * -1
105
+ end
106
+ )`,
107
+
108
+ // (y1 - y0) * 12 + (m1 - m0) + (t1 < t0) * -1
109
+ /* '%d%H%M%S%f' returns as a number like which results in an equal check to:
110
+ (
111
+ d1 < d0 ||
112
+ (d1 = d0 && h1 < h0) ||
113
+ (d1 = d0 && h1 = h0 && m1 < m0) ||
114
+ (d1 = d0 && h1 = h0 && m1 = m0 && s1 < s0) ||
115
+ (d1 = d0 && h1 = h0 && m1 = m0 && s1 = s0 && ms1 < ms0)
116
+ )
117
+ Which will remove the current month if the time of the month is below the time of the month of the start date
118
+ It should not matter that the number of days in the month is different as for a month to have passed
119
+ the time of the month would have to be higher then the time of the month of the start date
120
+
121
+ Also check whether the result will be positive or negative to make sure to not subtract an extra month
122
+ */
123
+ months_between : (x,y) => `
124
+ (
125
+ (
126
+ ( cast( strftime('%Y', ${y}) as Integer ) - cast( strftime('%Y', ${x}) as Integer ) ) * 12
127
+ ) + (
128
+ cast( strftime('%m', ${y}) as Integer ) - cast( strftime('%m', ${x}) as Integer )
129
+ ) + (
130
+ (
131
+ case
132
+ when ( cast( strftime('%Y%m', ${y}) as Integer ) < cast( strftime('%Y%m', ${x}) as Integer ) ) then
133
+ (cast( strftime('%d%H%M%S%f', ${y}) as Integer ) > cast( strftime('%d%H%M%S%f', ${x}) as Integer ))
134
+ else
135
+ (cast( strftime('%d%H%M%S%f', ${y}) as Integer ) < cast( strftime('%d%H%M%S%f', ${x}) as Integer )) * -1
136
+ end
137
+ )
138
+ )
139
+ )`,
140
+ years_between(x,y) { return `floor(${this.months_between(x,y)} / 12)` },
141
+ }
142
+
143
+ for (let each in HANAFunctions)
144
+ HANAFunctions[each.toUpperCase()] = HANAFunctions[each]
145
+
146
+ module.exports = { ...StandardFunctions, ...HANAFunctions }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * For operators of <eqOps>, this is replaced by comparing all leaf elements with null, combined with and.
3
+ * If there are at least two leaf elements and if there are tokens before or after the recognized pattern, we enclose the resulting condition in parens (...)
4
+ */
5
+ const eqOps = [['is'], ['='], /* ['=='] */ ]
6
+ /**
7
+ * For operators of <notEqOps>, do the same but use or instead of and.
8
+ * This ensures that not struc == <value> is the same as struc != <value>.
9
+ */
10
+ const notEqOps = [['is', 'not'], ['<>'], ['!=']]
11
+ /**
12
+ * not supported in comparison w/ struct because of unclear semantics
13
+ */
14
+ const notSupportedOps = [['>'], ['<'], ['>='], ['<=']]
15
+
16
+ module.exports = { eqOps, notEqOps, notSupportedOps }
@@ -0,0 +1,22 @@
1
+ const cds = require('@sap/cds/lib')
2
+
3
+ const target_name4 = q => {
4
+ const target = (
5
+ q.SELECT?.from ||
6
+ q.INSERT?.into ||
7
+ q.UPSERT?.into ||
8
+ q.UPDATE?.entity ||
9
+ q.DELETE?.from ||
10
+ q.CREATE?.entity ||
11
+ q.DROP?.entity ||
12
+ undefined
13
+ )
14
+ if (target?.SET?.op === "union") throw new cds.error('”UNION” based queries are not supported');
15
+ if (!target?.ref) return target
16
+ const [first] = target.ref
17
+ return first.id || first
18
+ }
19
+
20
+ module.exports = {
21
+ target_name4
22
+ }
@@ -0,0 +1,73 @@
1
+ const { resolveView } = require('@sap/cds/libx/_runtime/common/utils/resolveView')
2
+ const cds = require('@sap/cds')
3
+ const propagateForeignKeys = require('@sap/cds/libx/_runtime/common/utils/propagateForeignKeys')
4
+ const { enrichDataWithKeysFromWhere } = require('@sap/cds/libx/_runtime/common/utils/keys')
5
+
6
+ const generateUUIDandPropagateKeys = (target, data, event) => {
7
+ if (!data) return
8
+ const keys = target.keys
9
+ for (const key in keys) {
10
+ if (keys[key].type === 'cds.UUID' && !data[key] && event === 'CREATE') {
11
+ data[key] = cds.utils.uuid()
12
+ }
13
+ }
14
+ const elements = target.elements
15
+ for (const element in elements) {
16
+ // if assoc keys are structured, do not ignore them, as they need to be flattened in propagateForeignKeys
17
+ if (elements[element].key && !(elements[element]._isAssociationStrict && elements[element].is2one && element in data)) {
18
+ continue
19
+ }
20
+
21
+ if (elements[element].is2one || elements[element].is2many) {
22
+ let subData = data[element]
23
+ if (subData) {
24
+ if (!Array.isArray(subData)) {
25
+ subData = [subData]
26
+ }
27
+ for (const sub of subData) {
28
+ generateUUIDandPropagateKeys(elements[element]._target, sub, event)
29
+ }
30
+ }
31
+
32
+ propagateForeignKeys(element, data, elements[element]._foreignKeys, elements[element].isComposition, {
33
+ deleteAssocs: true
34
+ })
35
+ }
36
+ }
37
+ }
38
+
39
+ const input = async function (req, next) {
40
+ // REVISIT dummy handler until we have input processing
41
+ if (!req.target || !this.model) return next()
42
+
43
+ if (req.event === "UPDATE") {
44
+ // REVISIT for deep update we need to inject the keys first
45
+ enrichDataWithKeysFromWhere(req.data, req, this)
46
+ }
47
+
48
+ // REVISIT no input processing for INPUT with rows/values
49
+ if (req.event !== 'DELETE' && !(req.query.INSERT?.rows || req.query.INSERT?.values)) {
50
+ if (Array.isArray(req.data)) {
51
+ for (const d of req.data) {
52
+ generateUUIDandPropagateKeys(req.target, d, req.event)
53
+ }
54
+ } else {
55
+ generateUUIDandPropagateKeys(req.target, req.data, req.event)
56
+ }
57
+ }
58
+
59
+ const cmd = req.query.cmd || Object.keys(req.query)[0]
60
+
61
+ const resolved = resolveView(req.query, this.model, this)
62
+
63
+ if (resolved && resolved[cmd]._transitions?.[0].target) {
64
+ req.query = resolved || req.query
65
+ req.target = resolved?.[cmd]._transitions[0].target
66
+ }
67
+
68
+ return next()
69
+ }
70
+
71
+ module.exports = {
72
+ input
73
+ }
@@ -0,0 +1,149 @@
1
+ {
2
+ "ABORT":1,
3
+ "ACTION":1,
4
+ "ADD":1,
5
+ "AFTER":1,
6
+ "ALL":1,
7
+ "ALTER":1,
8
+ "ALWAYS":1,
9
+ "ANALYZE":1,
10
+ "AND":1,
11
+ "AS":1,
12
+ "ASC":1,
13
+ "ATTACH":1,
14
+ "AUTOINCREMENT":1,
15
+ "BEFORE":1,
16
+ "BEGIN":1,
17
+ "BETWEEN":1,
18
+ "BY":1,
19
+ "CASCADE":1,
20
+ "CASE":1,
21
+ "CAST":1,
22
+ "CHECK":1,
23
+ "COLLATE":1,
24
+ "COLUMN":1,
25
+ "COMMIT":1,
26
+ "CONFLICT":1,
27
+ "CONSTRAINT":1,
28
+ "CREATE":1,
29
+ "CROSS":1,
30
+ "CURRENT":1,
31
+ "CURRENT_DATE":1,
32
+ "CURRENT_TIME":1,
33
+ "CURRENT_TIMESTAMP":1,
34
+ "DATABASE":1,
35
+ "DEFAULT":1,
36
+ "DEFERRABLE":1,
37
+ "DEFERRED":1,
38
+ "DELETE":1,
39
+ "DESC":1,
40
+ "DETACH":1,
41
+ "DISTINCT":1,
42
+ "DO":1,
43
+ "DROP":1,
44
+ "EACH":1,
45
+ "ELSE":1,
46
+ "END":1,
47
+ "ESCAPE":1,
48
+ "EXCEPT":1,
49
+ "EXCLUDE":1,
50
+ "EXCLUSIVE":1,
51
+ "EXISTS":1,
52
+ "EXPLAIN":1,
53
+ "FAIL":1,
54
+ "FILTER":1,
55
+ "FIRST":1,
56
+ "FOLLOWING":1,
57
+ "FOR":1,
58
+ "FOREIGN":1,
59
+ "FROM":1,
60
+ "FULL":1,
61
+ "GENERATED":1,
62
+ "GLOB":1,
63
+ "GROUP":1,
64
+ "GROUPS":1,
65
+ "HAVING":1,
66
+ "IF":1,
67
+ "IGNORE":1,
68
+ "IMMEDIATE":1,
69
+ "IN":1,
70
+ "INDEX":1,
71
+ "INDEXED":1,
72
+ "INITIALLY":1,
73
+ "INNER":1,
74
+ "INSERT":1,
75
+ "INSTEAD":1,
76
+ "INTERSECT":1,
77
+ "INTO":1,
78
+ "IS":1,
79
+ "ISNULL":1,
80
+ "JOIN":1,
81
+ "KEY":1,
82
+ "LAST":1,
83
+ "LEFT":1,
84
+ "LIKE":1,
85
+ "LIMIT":1,
86
+ "MATCH":1,
87
+ "MATERIALIZED":1,
88
+ "NATURAL":1,
89
+ "NO":1,
90
+ "NOT":1,
91
+ "NOTHING":1,
92
+ "NOTNULL":1,
93
+ "NULL":1,
94
+ "NULLS":1,
95
+ "OF":1,
96
+ "OFFSET":1,
97
+ "ON":1,
98
+ "OR":1,
99
+ "ORDER":1,
100
+ "OTHERS":1,
101
+ "OUTER":1,
102
+ "OVER":1,
103
+ "PARTITION":1,
104
+ "PLAN":1,
105
+ "PRAGMA":1,
106
+ "PRECEDING":1,
107
+ "PRIMARY":1,
108
+ "QUERY":1,
109
+ "RAISE":1,
110
+ "RANGE":1,
111
+ "RECURSIVE":1,
112
+ "REFERENCES":1,
113
+ "REGEXP":1,
114
+ "REINDEX":1,
115
+ "RELEASE":1,
116
+ "RENAME":1,
117
+ "REPLACE":1,
118
+ "RESTRICT":1,
119
+ "RETURNING":1,
120
+ "RIGHT":1,
121
+ "ROLLBACK":1,
122
+ "ROW":1,
123
+ "ROWS":1,
124
+ "SAVEPOINT":1,
125
+ "SELECT":1,
126
+ "SET":1,
127
+ "TABLE":1,
128
+ "TEMP":1,
129
+ "TEMPORARY":1,
130
+ "THEN":1,
131
+ "TIES":1,
132
+ "TO":1,
133
+ "TRANSACTION":1,
134
+ "TRIGGER":1,
135
+ "UNBOUNDED":1,
136
+ "UNION":1,
137
+ "UNIQUE":1,
138
+ "UPDATE":1,
139
+ "USING":1,
140
+ "VACUUM":1,
141
+ "VALUES":1,
142
+ "VIEW":1,
143
+ "VIRTUAL":1,
144
+ "WHEN":1,
145
+ "WHERE":1,
146
+ "WINDOW":1,
147
+ "WITH":1,
148
+ "WITHOUT":1
149
+ }