json-fs-query 3.2.0 → 3.3.1

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.
Files changed (124) hide show
  1. package/README.md +265 -17
  2. package/browser/jsonFsQuery.js +2274 -305
  3. package/browser/jsonFsQuery.min.js +1 -1
  4. package/dist/data/introspect/alter.d.ts +12 -0
  5. package/dist/data/introspect/alter.js +304 -0
  6. package/dist/data/introspect/alter.min.js +1 -0
  7. package/dist/data/introspect/create.d.ts +10 -0
  8. package/dist/data/introspect/create.js +8 -2
  9. package/dist/data/introspect/create.min.js +1 -1
  10. package/dist/data/introspect/drop.js +2 -0
  11. package/dist/data/introspect/drop.min.js +1 -1
  12. package/dist/data/introspect/formatDatabase.d.ts +17 -0
  13. package/dist/data/introspect/formatDatabase.js +56 -0
  14. package/dist/data/introspect/formatDatabase.min.js +1 -0
  15. package/dist/data/introspect/index.d.ts +9 -0
  16. package/dist/data/introspect/index.js +18 -5
  17. package/dist/data/introspect/index.min.js +1 -1
  18. package/dist/data/introspect/packDatabase.d.ts +27 -0
  19. package/dist/data/introspect/packDatabase.js +113 -0
  20. package/dist/data/introspect/packDatabase.min.js +1 -0
  21. package/dist/data/queries/databaseStatements.d.ts +31 -0
  22. package/dist/data/queries/databaseStatements.js +97 -0
  23. package/dist/data/queries/databaseStatements.min.js +1 -0
  24. package/dist/data/queries/deleteEntity.js +6 -1
  25. package/dist/data/queries/deleteEntity.min.js +1 -1
  26. package/dist/data/queries/insertEntity.d.ts +8 -0
  27. package/dist/data/queries/insertEntity.js +342 -33
  28. package/dist/data/queries/insertEntity.min.js +1 -1
  29. package/dist/data/queries/query.d.ts +2 -1
  30. package/dist/data/queries/query.js +5 -2
  31. package/dist/data/queries/query.min.js +1 -1
  32. package/dist/data/queries/selectFields.d.ts +5 -4
  33. package/dist/data/queries/selectFields.js +25 -11
  34. package/dist/data/queries/selectFields.min.js +1 -1
  35. package/dist/data/queries/updateEntity.js +39 -5
  36. package/dist/data/queries/updateEntity.min.js +1 -1
  37. package/dist/data/storage/databases.d.ts +32 -0
  38. package/dist/data/storage/databases.js +93 -0
  39. package/dist/data/storage/databases.min.js +1 -0
  40. package/dist/data/storage/fileExists.d.ts +2 -1
  41. package/dist/data/storage/fileExists.js +9 -2
  42. package/dist/data/storage/fileExists.min.js +1 -1
  43. package/dist/data/storage/index.d.ts +5 -2
  44. package/dist/data/storage/index.js +64 -1
  45. package/dist/data/storage/index.min.js +1 -1
  46. package/dist/data/storage/listFiles.d.ts +8 -0
  47. package/dist/data/storage/listFiles.js +52 -0
  48. package/dist/data/storage/listFiles.min.js +1 -0
  49. package/dist/data/storage/makeDirectory.d.ts +2 -1
  50. package/dist/data/storage/makeDirectory.js +8 -2
  51. package/dist/data/storage/makeDirectory.min.js +1 -1
  52. package/dist/data/storage/readJson.d.ts +9 -1
  53. package/dist/data/storage/readJson.js +20 -7
  54. package/dist/data/storage/readJson.min.js +1 -1
  55. package/dist/data/storage/removeDirectory.d.ts +3 -1
  56. package/dist/data/storage/removeDirectory.js +7 -1
  57. package/dist/data/storage/removeDirectory.min.js +1 -1
  58. package/dist/data/storage/removeFile.d.ts +2 -1
  59. package/dist/data/storage/removeFile.js +10 -1
  60. package/dist/data/storage/removeFile.min.js +1 -1
  61. package/dist/data/storage/storagePath.js +1 -1
  62. package/dist/data/storage/storagePath.min.js +1 -1
  63. package/dist/data/storage/writeJson.d.ts +12 -3
  64. package/dist/data/storage/writeJson.js +21 -7
  65. package/dist/data/storage/writeJson.min.js +1 -1
  66. package/dist/data/storage/writeSet.d.ts +64 -0
  67. package/dist/data/storage/writeSet.js +306 -0
  68. package/dist/data/storage/writeSet.min.js +1 -0
  69. package/dist/data/utilities/config.d.ts +37 -0
  70. package/dist/data/utilities/config.js +77 -5
  71. package/dist/data/utilities/config.min.js +1 -1
  72. package/dist/data/utilities/databaseLock.d.ts +5 -0
  73. package/dist/data/utilities/databaseLock.js +15 -3
  74. package/dist/data/utilities/databaseLock.min.js +1 -1
  75. package/dist/data/utilities/getConditionIndexedList.js +3 -4
  76. package/dist/data/utilities/getConditionIndexedList.min.js +1 -1
  77. package/dist/data/utilities/indexBuffer.d.ts +35 -0
  78. package/dist/data/utilities/indexBuffer.js +139 -0
  79. package/dist/data/utilities/indexBuffer.min.js +1 -0
  80. package/dist/data/utilities/joinBases.js +2 -10
  81. package/dist/data/utilities/joinBases.min.js +1 -1
  82. package/dist/data/utilities/knownFields.d.ts +26 -0
  83. package/dist/data/utilities/knownFields.js +128 -0
  84. package/dist/data/utilities/knownFields.min.js +1 -0
  85. package/dist/data/utilities/packedTable.d.ts +34 -0
  86. package/dist/data/utilities/packedTable.js +60 -0
  87. package/dist/data/utilities/packedTable.min.js +1 -0
  88. package/dist/data/utilities/parsers/findInsertValues.d.ts +3 -3
  89. package/dist/data/utilities/parsers/findInsertValues.js +28 -12
  90. package/dist/data/utilities/parsers/findInsertValues.min.js +1 -1
  91. package/dist/data/utilities/parsers/maskStrings.js +11 -2
  92. package/dist/data/utilities/parsers/maskStrings.min.js +1 -1
  93. package/dist/data/utilities/parsers/parseAlter.d.ts +33 -0
  94. package/dist/data/utilities/parsers/parseAlter.js +84 -0
  95. package/dist/data/utilities/parsers/parseAlter.min.js +1 -0
  96. package/dist/data/utilities/parsers/parseCreate.d.ts +14 -0
  97. package/dist/data/utilities/parsers/parseCreate.js +13 -5
  98. package/dist/data/utilities/parsers/parseCreate.min.js +1 -1
  99. package/dist/data/utilities/parsers/parseDatabaseStatement.d.ts +17 -0
  100. package/dist/data/utilities/parsers/parseDatabaseStatement.js +82 -0
  101. package/dist/data/utilities/parsers/parseDatabaseStatement.min.js +1 -0
  102. package/dist/data/utilities/parsers/parseQuery.d.ts +3 -0
  103. package/dist/data/utilities/parsers/parseQuery.js +24 -0
  104. package/dist/data/utilities/parsers/parseQuery.min.js +1 -1
  105. package/dist/data/utilities/parsers/startsQuery.js +1 -1
  106. package/dist/data/utilities/parsers/startsQuery.min.js +1 -1
  107. package/dist/data/utilities/reconcileJoin.d.ts +12 -0
  108. package/dist/data/utilities/reconcileJoin.js +30 -10
  109. package/dist/data/utilities/reconcileJoin.min.js +1 -1
  110. package/dist/data/utilities/retrieveFiles.d.ts +15 -0
  111. package/dist/data/utilities/retrieveFiles.js +48 -0
  112. package/dist/data/utilities/retrieveFiles.min.js +1 -0
  113. package/dist/data/utilities/retrieveRecord.d.ts +6 -0
  114. package/dist/data/utilities/retrieveRecord.js +24 -1
  115. package/dist/data/utilities/retrieveRecord.min.js +1 -1
  116. package/dist/data/utilities/retrieveRecords.d.ts +22 -0
  117. package/dist/data/utilities/retrieveRecords.js +60 -10
  118. package/dist/data/utilities/retrieveRecords.min.js +1 -1
  119. package/dist/data/utilities/runQuery.js +27 -2
  120. package/dist/data/utilities/runQuery.min.js +1 -1
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.js +24 -0
  123. package/dist/index.min.js +1 -1
  124. package/package.json +8 -4
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.assertKnownFields=exports.assertFieldsDefined=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.filter.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.flat-map.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js");var _retrieveRecord=require("./retrieveRecord"),_isEntityProperty=require("./parsers/isEntityProperty"),_splitEntityProperty=require("./parsers/splitEntityProperty"),_conditionTree=require("./conditionTree");const fieldList=e=>e.definition.map((e=>e.name)).join(", "),assertFieldsDefined=(e,r,s,t)=>{const o=new Set(r.definition.map((e=>e.name))),i=s.find((e=>!o.has(e)));if(void 0!==i)throw new Error(`Unknown field "${i}" in ${t}: ${e} has ${fieldList(r)}`)};exports.assertFieldsDefined=assertFieldsDefined;const assertKnownFields=async(e,r)=>{const s=new Set((e.mergeJoins||[]).map((e=>(0,_splitEntityProperty.splitEntityProperty)(e.propertyB).entity))),t=[...(0,_conditionTree.conditionsOf)(e.where).map((e=>({property:e.property,clause:"where"}))),...(e.sortClauses||[]).map((e=>({property:e.property,clause:"sort"}))),...(e.groupBy||[]).map((e=>({property:e,clause:"group by"}))),...(e.selectClauses||[]).filter((r=>r.property!==e.joinEntity)).map((e=>({property:e.property,clause:"select"}))),...(e.insertValues||[]).flatMap((e=>Object.keys(e||{}).map((e=>({property:e,clause:"insert",own:!0}))))),...Object.keys(e.updateValues||{}).map((e=>({property:e,clause:"set",own:!0}))),...(e.joinClauses||[]).flatMap((e=>[{property:e.propertyA,clause:"on"},{property:e.propertyB,clause:"on"}])),...(e.mergeJoins||[]).flatMap((e=>[{property:e.propertyA,clause:"merge"},{property:e.propertyB,clause:"merge"}]))];for(const{property:o,clause:i,own:n}of t){if("string"!=typeof o)continue;let t=e.entity,p=o;if(!n&&(0,_isEntityProperty.isEntityProperty)(o)&&({entity:t,property:p}=(0,_splitEntityProperty.splitEntityProperty)(o)),"select"===i&&t!==e.entity&&!s.has(t))throw new Error(`Can't select "${o}": a joined record's rows are a list under its name (select ${t}), and its fields are on each row only when merged (merge ... with ${t}.<field>)`);Object.prototype.hasOwnProperty.call(r,t)||(r[t]=await(0,_retrieveRecord.retrieveRecord)(t)),assertFieldsDefined(t,r[t],[p],i)}};exports.assertKnownFields=assertKnownFields;
@@ -0,0 +1,34 @@
1
+ import { recordDefinition } from '../introspect/definitions/record';
2
+ /**
3
+ * One record packed into one file by packDatabase: its entry files' names and rows, in the record's order, so a browser
4
+ * reads a whole record in one request instead of one per row. Indexes stay in their own (small) files: a lookup that
5
+ * needs a few rows reads the index and those rows, not the whole record.
6
+ */
7
+ export type packedTable = {
8
+ entries: string[];
9
+ rows: any[];
10
+ };
11
+ /**
12
+ * Where a record's packed file lives, inside the database.
13
+ * @param recordPath
14
+ */
15
+ export declare const packedPath: (recordPath: string) => string;
16
+ /**
17
+ * Whether reads may use packed files: by default in a browser (where every file is a request) and not in Node (which
18
+ * reads the entry files, the copy that's edited, so a packed file can never hide a change there). See configure.
19
+ */
20
+ export declare const usesPackedReads: () => boolean;
21
+ /**
22
+ * A record's packed file, when reads may use packed files (see usesPackedReads) and the record has one that matches it:
23
+ * the same entries in the same order as the record file. Anything else (no packed file, one that can't be read, or one
24
+ * packed before entries were added or removed) gives undefined, and the caller reads the entry files as before.
25
+ * @param record
26
+ */
27
+ export declare const retrievePacked: (record: recordDefinition) => Promise<packedTable | undefined>;
28
+ /**
29
+ * Remove a record's packed file, if it has one, as part of a statement that changes the record's rows: a packed file
30
+ * is a copy of them, and one left behind after a change would be read in a browser in place of the change.
31
+ * @param recordPath
32
+ */
33
+ export declare const discardPacked: (recordPath: string) => Promise<void>;
34
+ //# sourceMappingURL=packedTable.d.ts.map
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+
3
+ require("core-js/modules/esnext.weak-map.delete-all.js");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.usesPackedReads = exports.retrievePacked = exports.packedPath = exports.discardPacked = void 0;
8
+ require("core-js/modules/esnext.iterator.constructor.js");
9
+ require("core-js/modules/esnext.iterator.some.js");
10
+ var jsEnv = _interopRequireWildcard(require("browser-or-node"));
11
+ var _storage = require("../storage");
12
+ var _config = require("./config");
13
+ var e;
14
+ function _interopRequireWildcard(t, r) { if (!r && t && t.__esModule) return t; var n, a, o = Object.defineProperty, f = { __proto__: null, default: t }; if (Object(t) !== t) return f; if (e || "function" != typeof WeakMap || (e = new WeakMap()), e) { if (e.has(t)) return e.get(t); e.set(t, f); } for (a in t) "default" !== a && {}.hasOwnProperty.call(t, a) && ((n = o && Object.getOwnPropertyDescriptor(t, a)) && (n.get || n.set) ? o(f, a, n) : f[a] = t[a]); return f; }
15
+ /**
16
+ * Where a record's packed file lives, inside the database.
17
+ * @param recordPath
18
+ */
19
+ const packedPath = recordPath => `__packed/${recordPath}.json`;
20
+ /**
21
+ * Whether reads may use packed files: by default in a browser (where every file is a request) and not in Node (which
22
+ * reads the entry files, the copy that's edited, so a packed file can never hide a change there). See configure.
23
+ */
24
+ exports.packedPath = packedPath;
25
+ const usesPackedReads = () => (0, _config.getSetting)('packedReads', !jsEnv.isNode);
26
+ /**
27
+ * A record's packed file, when reads may use packed files (see usesPackedReads) and the record has one that matches it:
28
+ * the same entries in the same order as the record file. Anything else (no packed file, one that can't be read, or one
29
+ * packed before entries were added or removed) gives undefined, and the caller reads the entry files as before.
30
+ * @param record
31
+ */
32
+ exports.usesPackedReads = usesPackedReads;
33
+ const retrievePacked = async record => {
34
+ if (!usesPackedReads()) {
35
+ return undefined;
36
+ }
37
+ let packed;
38
+ try {
39
+ packed = await (0, _storage.readJson)(packedPath(record.path));
40
+ } catch (error) {
41
+ return undefined;
42
+ }
43
+ const entries = record.entries || [];
44
+ if (!packed || !Array.isArray(packed.entries) || !Array.isArray(packed.rows) || packed.entries.length !== entries.length || packed.rows.length !== entries.length || packed.entries.some((entry, position) => entry !== entries[position])) {
45
+ return undefined;
46
+ }
47
+ return packed;
48
+ };
49
+ /**
50
+ * Remove a record's packed file, if it has one, as part of a statement that changes the record's rows: a packed file
51
+ * is a copy of them, and one left behind after a change would be read in a browser in place of the change.
52
+ * @param recordPath
53
+ */
54
+ exports.retrievePacked = retrievePacked;
55
+ const discardPacked = async recordPath => {
56
+ if ((0, _storage.fileExists)(packedPath(recordPath))) {
57
+ await (0, _storage.removeFile)(packedPath(recordPath));
58
+ }
59
+ };
60
+ exports.discardPacked = discardPacked;
@@ -0,0 +1 @@
1
+ "use strict";require("core-js/modules/esnext.weak-map.delete-all.js"),Object.defineProperty(exports,"__esModule",{value:!0}),exports.usesPackedReads=exports.retrievePacked=exports.packedPath=exports.discardPacked=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.some.js");var e,jsEnv=_interopRequireWildcard(require("browser-or-node")),_storage=require("../storage"),_config=require("./config");function _interopRequireWildcard(r,t){if(!t&&r&&r.__esModule)return r;var s,a,o=Object.defineProperty,d={__proto__:null,default:r};if(Object(r)!==r)return d;if(e||"function"!=typeof WeakMap||(e=new WeakMap),e){if(e.has(r))return e.get(r);e.set(r,d)}for(a in r)"default"!==a&&{}.hasOwnProperty.call(r,a)&&((s=o&&Object.getOwnPropertyDescriptor(r,a))&&(s.get||s.set)?o(d,a,s):d[a]=r[a]);return d}const packedPath=e=>`__packed/${e}.json`;exports.packedPath=packedPath;const usesPackedReads=()=>(0,_config.getSetting)("packedReads",!jsEnv.isNode);exports.usesPackedReads=usesPackedReads;const retrievePacked=async e=>{if(!usesPackedReads())return;let r;try{r=await(0,_storage.readJson)(packedPath(e.path))}catch(e){return}const t=e.entries||[];return r&&Array.isArray(r.entries)&&Array.isArray(r.rows)&&r.entries.length===t.length&&r.rows.length===t.length&&!r.entries.some(((e,r)=>e!==t[r]))?r:void 0};exports.retrievePacked=retrievePacked;const discardPacked=async e=>{(0,_storage.fileExists)(packedPath(e))&&await(0,_storage.removeFile)(packedPath(e))};exports.discardPacked=discardPacked;
@@ -3,9 +3,9 @@ export type ParsedInsertRow = {
3
3
  [field: string]: any;
4
4
  };
5
5
  /**
6
- * Read an insert's values clause body (`field = value, field2 = value2`, see splitClauses), the same way
7
- * findUpdateValues() does for "set". insertEntity() accepts an array of rows for bulk inserts, but
8
- * a query string only describes a single row for now - wrapped in a one-element array to match.
6
+ * Read an insert's values clause body (see splitClauses), the same way findUpdateValues() does for "set": one row
7
+ * (`field = value, field2 = value2`), or several, each in brackets and separated by commas
8
+ * (`(field = value, field2 = value2), (field = value3, field2 = value4)`), inserted together in one statement.
9
9
  * @param parsed
10
10
  * @param body
11
11
  * @returns The rest of the body, after the last assignment.
@@ -7,29 +7,45 @@ exports.findInsertValues = void 0;
7
7
  var _parseLiteralValue = require("./parseLiteralValue");
8
8
  var _maskStrings = require("./maskStrings");
9
9
  const literal = `(?:${_maskStrings.placeholderSource.replace('(', '(?:')}|${_parseLiteralValue.numberSource}|${_maskStrings.arraySource.replace('(', '(?:')}|null|true|false)`;
10
- const valuesMatch = new RegExp(`^([a-z0-9_-]+\\s*=\\s*${literal}(?:\\s*,\\s*[a-z0-9_-]+\\s*=\\s*${literal})*)`, 'i');
10
+ const assignments = `[a-z0-9_-]+\\s*=\\s*${literal}(?:\\s*,\\s*[a-z0-9_-]+\\s*=\\s*${literal})*`;
11
+ const valuesMatch = new RegExp(`^(${assignments})`, 'i');
12
+ const rowSource = `\\(\\s*(${assignments})\\s*\\)`;
13
+ const rowsMatch = new RegExp(`^${rowSource}(?:\\s*,\\s*${rowSource.replace('(' + assignments, '(?:' + assignments)})*`, 'i');
14
+ const rowMatch = new RegExp(rowSource, 'ig');
11
15
  const assignmentMatch = new RegExp(`([a-z0-9_-]+)\\s*=\\s*(${_maskStrings.placeholderSource}|(${_parseLiteralValue.numberSource})|${_maskStrings.arraySource}|(null)|(true)|(false))`, 'ig');
12
16
  /**
13
- * Read an insert's values clause body (`field = value, field2 = value2`, see splitClauses), the same way
14
- * findUpdateValues() does for "set". insertEntity() accepts an array of rows for bulk inserts, but
15
- * a query string only describes a single row for now - wrapped in a one-element array to match.
17
+ * One row's values from its assignments (`field = value, field2 = value2`).
18
+ * @param assignments
19
+ */
20
+ const parseRow = assignments => {
21
+ const row = {};
22
+ // @ts-ignore
23
+ for (const assignment of assignments.matchAll(assignmentMatch)) {
24
+ row[assignment[1]] = (0, _parseLiteralValue.parseLiteralValue)(assignment, 3);
25
+ }
26
+ return row;
27
+ };
28
+ /**
29
+ * Read an insert's values clause body (see splitClauses), the same way findUpdateValues() does for "set": one row
30
+ * (`field = value, field2 = value2`), or several, each in brackets and separated by commas
31
+ * (`(field = value, field2 = value2), (field = value3, field2 = value4)`), inserted together in one statement.
16
32
  * @param parsed
17
33
  * @param body
18
34
  * @returns The rest of the body, after the last assignment.
19
35
  */
20
36
  const findInsertValues = (parsed, body) => {
21
- const clauseFound = body.match(valuesMatch);
22
37
  parsed['insertValues'] = [];
38
+ const rowsFound = body.match(rowsMatch);
39
+ if (rowsFound) {
40
+ // @ts-ignore
41
+ parsed['insertValues'] = [...rowsFound[0].matchAll(rowMatch)].map(row => parseRow(row[1]));
42
+ return body.slice(rowsFound[0].length).trim();
43
+ }
44
+ const clauseFound = body.match(valuesMatch);
23
45
  if (!clauseFound) {
24
46
  return body;
25
47
  }
26
- const row = {};
27
- const assignmentsFound = clauseFound[1].matchAll(assignmentMatch);
28
- // @ts-ignore
29
- for (const assignment of assignmentsFound) {
30
- row[assignment[1]] = (0, _parseLiteralValue.parseLiteralValue)(assignment, 3);
31
- }
32
- parsed['insertValues'] = [row];
48
+ parsed['insertValues'] = [parseRow(clauseFound[1])];
33
49
  return body.slice(clauseFound[0].length).trim();
34
50
  };
35
51
  exports.findInsertValues = findInsertValues;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findInsertValues=void 0;var _parseLiteralValue=require("./parseLiteralValue"),_maskStrings=require("./maskStrings");const literal=`(?:${_maskStrings.placeholderSource.replace("(","(?:")}|${_parseLiteralValue.numberSource}|${_maskStrings.arraySource.replace("(","(?:")}|null|true|false)`,valuesMatch=new RegExp(`^([a-z0-9_-]+\\s*=\\s*${literal}(?:\\s*,\\s*[a-z0-9_-]+\\s*=\\s*${literal})*)`,"i"),assignmentMatch=new RegExp(`([a-z0-9_-]+)\\s*=\\s*(${_maskStrings.placeholderSource}|(${_parseLiteralValue.numberSource})|${_maskStrings.arraySource}|(null)|(true)|(false))`,"ig"),findInsertValues=(e,r)=>{const a=r.match(valuesMatch);if(e.insertValues=[],!a)return r;const s={},t=a[1].matchAll(assignmentMatch);for(const e of t)s[e[1]]=(0,_parseLiteralValue.parseLiteralValue)(e,3);return e.insertValues=[s],r.slice(a[0].length).trim()};exports.findInsertValues=findInsertValues;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findInsertValues=void 0;var _parseLiteralValue=require("./parseLiteralValue"),_maskStrings=require("./maskStrings");const literal=`(?:${_maskStrings.placeholderSource.replace("(","(?:")}|${_parseLiteralValue.numberSource}|${_maskStrings.arraySource.replace("(","(?:")}|null|true|false)`,assignments=`[a-z0-9_-]+\\s*=\\s*${literal}(?:\\s*,\\s*[a-z0-9_-]+\\s*=\\s*${literal})*`,valuesMatch=new RegExp(`^(${assignments})`,"i"),rowSource=`\\(\\s*(${assignments})\\s*\\)`,rowsMatch=new RegExp(`^${rowSource}(?:\\s*,\\s*${rowSource.replace("("+assignments,"(?:"+assignments)})*`,"i"),rowMatch=new RegExp(rowSource,"ig"),assignmentMatch=new RegExp(`([a-z0-9_-]+)\\s*=\\s*(${_maskStrings.placeholderSource}|(${_parseLiteralValue.numberSource})|${_maskStrings.arraySource}|(null)|(true)|(false))`,"ig"),parseRow=e=>{const s={};for(const r of e.matchAll(assignmentMatch))s[r[1]]=(0,_parseLiteralValue.parseLiteralValue)(r,3);return s},findInsertValues=(e,s)=>{e.insertValues=[];const r=s.match(rowsMatch);if(r)return e.insertValues=[...r[0].matchAll(rowMatch)].map((e=>parseRow(e[1]))),s.slice(r[0].length).trim();const a=s.match(valuesMatch);return a?(e.insertValues=[parseRow(a[1])],s.slice(a[0].length).trim()):s};exports.findInsertValues=findInsertValues;
@@ -54,11 +54,20 @@ const maskStrings = query => {
54
54
  }
55
55
  }
56
56
  }
57
- return strings.reduceRight((masked, {
57
+ // One pass from the left (the strings come in order, without overlapping): rebuilding the whole query for each string
58
+ // made a long query (an insert of thousands of rows) take time growing with the square of its length
59
+ const parts = [];
60
+ let copied = 0;
61
+ for (const {
58
62
  start,
59
63
  end,
60
64
  value
61
- }) => `${masked.slice(0, start)}\u0000${toHex(value)}\u0000${masked.slice(end)}`, query);
65
+ } of strings) {
66
+ parts.push(query.slice(copied, start), `\u0000${toHex(value)}\u0000`);
67
+ copied = end;
68
+ }
69
+ parts.push(query.slice(copied));
70
+ return parts.join('');
62
71
  };
63
72
  /**
64
73
  * Put every placeholder back the way it would be written in a query: strings quoted, arrays as JSON. For error
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.unmaskStrings=exports.placeholderSource=exports.maskStrings=exports.maskArrays=exports.fromPlaceholder=exports.arraySource=void 0,require("core-js/modules/es.json.stringify.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.some.js");var _scanQuery=require("./scanQuery");const placeholderSource=exports.placeholderSource="\\u0000([0-9a-f]*)\\u0000",placeholderMatch=new RegExp(placeholderSource,"g"),arraySource=exports.arraySource="\\u0001([0-9a-f]*)\\u0001",arrayMatch=new RegExp(arraySource,"g"),toHex=r=>r.split("").map((r=>r.charCodeAt(0).toString(16).padStart(4,"0"))).join(""),fromPlaceholder=r=>(r.match(/.{4}/g)||[]).map((r=>String.fromCharCode(parseInt(r,16)))).join("");exports.fromPlaceholder=fromPlaceholder;const maskStrings=r=>{const{strings:e}=(0,_scanQuery.assertClosed)((0,_scanQuery.scanQuery)(r));for(const[s,t]of[["\0","NUL"],["","SOH"]])for(let o=r.indexOf(s);-1!==o;o=r.indexOf(s,o+1))if(!e.some((({start:r,end:e})=>r<o&&o<e)))throw new Error(`Unexpected ${t} character at position ${o}`);return e.reduceRight(((r,{start:e,end:s,value:t})=>`${r.slice(0,e)}\0${toHex(t)}\0${r.slice(s)}`),r)};exports.maskStrings=maskStrings;const unmaskStrings=(r,e="json")=>r.replace(placeholderMatch,((r,s)=>{const t=fromPlaceholder(s);return"json"===e?JSON.stringify(t):`'${t.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}'`})).replace(arrayMatch,((r,e)=>fromPlaceholder(e)));exports.unmaskStrings=unmaskStrings;const arrayJson=r=>{try{return JSON.stringify(JSON.parse(unmaskStrings(r),((r,e)=>{if("number"==typeof e&&!Number.isFinite(e))throw new Error(`${e} is too large`);if(Number.isInteger(e)&&!Number.isSafeInteger(e))throw new Error(`${e} is too large to store exactly (the largest whole number is ${Number.MAX_SAFE_INTEGER}): quote it to keep it as text`);return e})))}catch(e){throw new Error(`Invalid array ${unmaskStrings(r,"'")}: ${e.message}`)}},maskArrays=r=>{let e="",s=0,t=0;for(let o=0;o<r.length;o++){const a=r[o];if("["===a)0===s&&(t=o),s++;else if("]"===a&&s>0&&(s--,0===s)){e+=`${toHex(arrayJson(r.slice(t,o+1)))}`;continue}0===s&&(e+=a)}return s>0?e+r.slice(t):e};exports.maskArrays=maskArrays;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.unmaskStrings=exports.placeholderSource=exports.maskStrings=exports.maskArrays=exports.fromPlaceholder=exports.arraySource=void 0,require("core-js/modules/es.json.stringify.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.some.js");var _scanQuery=require("./scanQuery");const placeholderSource=exports.placeholderSource="\\u0000([0-9a-f]*)\\u0000",placeholderMatch=new RegExp(placeholderSource,"g"),arraySource=exports.arraySource="\\u0001([0-9a-f]*)\\u0001",arrayMatch=new RegExp(arraySource,"g"),toHex=r=>r.split("").map((r=>r.charCodeAt(0).toString(16).padStart(4,"0"))).join(""),fromPlaceholder=r=>(r.match(/.{4}/g)||[]).map((r=>String.fromCharCode(parseInt(r,16)))).join("");exports.fromPlaceholder=fromPlaceholder;const maskStrings=r=>{const{strings:e}=(0,_scanQuery.assertClosed)((0,_scanQuery.scanQuery)(r));for(const[s,t]of[["\0","NUL"],["","SOH"]])for(let o=r.indexOf(s);-1!==o;o=r.indexOf(s,o+1))if(!e.some((({start:r,end:e})=>r<o&&o<e)))throw new Error(`Unexpected ${t} character at position ${o}`);const s=[];let t=0;for(const{start:o,end:a,value:n}of e)s.push(r.slice(t,o),`\0${toHex(n)}\0`),t=a;return s.push(r.slice(t)),s.join("")};exports.maskStrings=maskStrings;const unmaskStrings=(r,e="json")=>r.replace(placeholderMatch,((r,s)=>{const t=fromPlaceholder(s);return"json"===e?JSON.stringify(t):`'${t.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}'`})).replace(arrayMatch,((r,e)=>fromPlaceholder(e)));exports.unmaskStrings=unmaskStrings;const arrayJson=r=>{try{return JSON.stringify(JSON.parse(unmaskStrings(r),((r,e)=>{if("number"==typeof e&&!Number.isFinite(e))throw new Error(`${e} is too large`);if(Number.isInteger(e)&&!Number.isSafeInteger(e))throw new Error(`${e} is too large to store exactly (the largest whole number is ${Number.MAX_SAFE_INTEGER}): quote it to keep it as text`);return e})))}catch(e){throw new Error(`Invalid array ${unmaskStrings(r,"'")}: ${e.message}`)}},maskArrays=r=>{let e="",s=0,t=0;for(let o=0;o<r.length;o++){const a=r[o];if("["===a)0===s&&(t=o),s++;else if("]"===a&&s>0&&(s--,0===s)){e+=`${toHex(arrayJson(r.slice(t,o+1)))}`;continue}0===s&&(e+=a)}return s>0?e+r.slice(t):e};exports.maskArrays=maskArrays;
@@ -0,0 +1,33 @@
1
+ import { fieldProperties } from '../../introspect/definitions/field';
2
+ /**
3
+ * One change to a record's fields (see alter):
4
+ * - `add`: a new field, as create defines one
5
+ * - `drop`: remove a field
6
+ * - `rename`: give a field a new name
7
+ * - `modify`: a field's new definition (the same name and type; optional, null and default can change)
8
+ */
9
+ export type ParsedAlter = {
10
+ action: 'add';
11
+ field: fieldProperties;
12
+ } | {
13
+ action: 'drop';
14
+ name: string;
15
+ } | {
16
+ action: 'rename';
17
+ name: string;
18
+ to: string;
19
+ } | {
20
+ action: 'modify';
21
+ field: fieldProperties;
22
+ };
23
+ export type ParsedAlterQuery = {
24
+ entity: string;
25
+ alter: ParsedAlter;
26
+ };
27
+ export declare const alterMatch: RegExp;
28
+ /**
29
+ * Read an alter statement (one change per statement; several statements in one query run in turn).
30
+ * @param query - The statement, with its strings and arrays masked (see maskStrings).
31
+ */
32
+ export declare const parseAlter: (query: string) => ParsedAlterQuery;
33
+ //# sourceMappingURL=parseAlter.d.ts.map
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.parseAlter = exports.alterMatch = void 0;
7
+ var _parseCreate = require("./parseCreate");
8
+ var _maskStrings = require("./maskStrings");
9
+ var _startsQuery = require("./startsQuery");
10
+ const alterMatch = exports.alterMatch = /^\s*alter(?![a-z0-9_-])/i;
11
+ const headMatch = new RegExp(`^\\s*alter\\s+(${_parseCreate.nameSource})\\s+(add|drop|rename|modify)(?![a-z0-9_-])([\\s\\S]*)$`, 'i');
12
+ const renameMatch = new RegExp(`^\\s*(${_parseCreate.nameSource})\\s+to\\s+(${_parseCreate.nameSource})\\s*$`, 'i');
13
+ const expected = 'Expected: alter <record> add <field> <type> ..., drop <field>, rename <field> to <name>, or modify <field> <type> ...';
14
+ const shown = text => (0, _maskStrings.unmaskStrings)(text.trim(), '\'');
15
+ /**
16
+ * A field as create's field list writes one (`<name> <type> [optional] [null | not null] [default <value>] [auto]`).
17
+ * @param text
18
+ */
19
+ const fieldSpec = text => {
20
+ const found = text.trim().match(_parseCreate.fieldMatch);
21
+ if (!found) {
22
+ throw new Error(`Expected a field (<name> <type> ...) in: ${shown(text)}`);
23
+ }
24
+ return (0, _parseCreate.parseField)(found);
25
+ };
26
+ /**
27
+ * Read an alter statement (one change per statement; several statements in one query run in turn).
28
+ * @param query - The statement, with its strings and arrays masked (see maskStrings).
29
+ */
30
+ const parseAlter = query => {
31
+ const head = query.match(headMatch);
32
+ if (!head) {
33
+ throw new Error(expected);
34
+ }
35
+ const [, entity, actionWord, rest] = head;
36
+ if (entity === '__RECORDS') {
37
+ throw new Error('__RECORDS is reserved for the list of records');
38
+ }
39
+ const action = actionWord.toLowerCase();
40
+ if (_startsQuery.startsQueryMatch.test(rest.trim()) && !/^\s*(add|drop|rename|modify)\b/i.test(rest)) {
41
+ throw new Error(`Missing ; before: ${shown(rest)}`);
42
+ }
43
+ switch (action) {
44
+ case 'add':
45
+ case 'modify':
46
+ return {
47
+ entity,
48
+ alter: {
49
+ action,
50
+ field: fieldSpec(rest)
51
+ }
52
+ };
53
+ case 'drop':
54
+ {
55
+ const name = rest.trim();
56
+ if (!_parseCreate.nameMatch.test(name)) {
57
+ throw new Error(`Expected: alter ${entity} drop <field>${name ? ` (got: ${shown(name)})` : ''}`);
58
+ }
59
+ return {
60
+ entity,
61
+ alter: {
62
+ action,
63
+ name
64
+ }
65
+ };
66
+ }
67
+ default:
68
+ {
69
+ const found = rest.match(renameMatch);
70
+ if (!found) {
71
+ throw new Error(`Expected: alter ${entity} rename <field> to <name>${rest.trim() ? ` (got: ${shown(rest)})` : ''}`);
72
+ }
73
+ return {
74
+ entity,
75
+ alter: {
76
+ action: 'rename',
77
+ name: found[1],
78
+ to: found[2]
79
+ }
80
+ };
81
+ }
82
+ }
83
+ };
84
+ exports.parseAlter = parseAlter;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.parseAlter=exports.alterMatch=void 0;var _parseCreate=require("./parseCreate"),_maskStrings=require("./maskStrings"),_startsQuery=require("./startsQuery");const alterMatch=exports.alterMatch=/^\s*alter(?![a-z0-9_-])/i,headMatch=new RegExp(`^\\s*alter\\s+(${_parseCreate.nameSource})\\s+(add|drop|rename|modify)(?![a-z0-9_-])([\\s\\S]*)$`,"i"),renameMatch=new RegExp(`^\\s*(${_parseCreate.nameSource})\\s+to\\s+(${_parseCreate.nameSource})\\s*$`,"i"),expected="Expected: alter <record> add <field> <type> ..., drop <field>, rename <field> to <name>, or modify <field> <type> ...",shown=e=>(0,_maskStrings.unmaskStrings)(e.trim(),"'"),fieldSpec=e=>{const r=e.trim().match(_parseCreate.fieldMatch);if(!r)throw new Error(`Expected a field (<name> <type> ...) in: ${shown(e)}`);return(0,_parseCreate.parseField)(r)},parseAlter=e=>{const r=e.match(headMatch);if(!r)throw new Error(expected);const[,t,a,s]=r;if("__RECORDS"===t)throw new Error("__RECORDS is reserved for the list of records");const o=a.toLowerCase();if(_startsQuery.startsQueryMatch.test(s.trim())&&!/^\s*(add|drop|rename|modify)\b/i.test(s))throw new Error(`Missing ; before: ${shown(s)}`);switch(o){case"add":case"modify":return{entity:t,alter:{action:o,field:fieldSpec(s)}};case"drop":{const e=s.trim();if(!_parseCreate.nameMatch.test(e))throw new Error(`Expected: alter ${t} drop <field>${e?` (got: ${shown(e)})`:""}`);return{entity:t,alter:{action:o,name:e}}}default:{const e=s.match(renameMatch);if(!e)throw new Error(`Expected: alter ${t} rename <field> to <name>${s.trim()?` (got: ${shown(s)})`:""}`);return{entity:t,alter:{action:"rename",name:e[1],to:e[2]}}}}};exports.parseAlter=parseAlter;
@@ -10,6 +10,20 @@ export type ParsedCreateQuery = {
10
10
  create: ParsedCreate;
11
11
  };
12
12
  export declare const createMatch: RegExp;
13
+ export declare const nameSource = "[a-z0-9_-]+";
14
+ export declare const nameMatch: RegExp;
15
+ export declare const fieldMatch: RegExp;
16
+ /**
17
+ * The index file name a key's index lives in, from its type and fields (as create names it).
18
+ * @param type
19
+ * @param fields
20
+ */
21
+ export declare const keyLookup: (type: string, fields: string[]) => string;
22
+ /**
23
+ * One field: `<name> <type> [optional] [null | not null] [auto] [default <value>]`, the modifiers in any order.
24
+ * @param found - The field's fieldMatch match.
25
+ */
26
+ export declare const parseField: (found: RegExpMatchArray) => fieldProperties;
13
27
  /**
14
28
  * Parse a create statement (strings and arrays already masked):
15
29
  * `create [if not exists] <name> (<field>, ..., <key>, ...)`. See the README's "Creating a record". Only checks
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.parseCreate = exports.createMatch = void 0;
6
+ exports.parseField = exports.parseCreate = exports.nameSource = exports.nameMatch = exports.keyLookup = exports.fieldMatch = exports.createMatch = void 0;
7
7
  require("core-js/modules/es.array.includes.js");
8
8
  require("core-js/modules/esnext.iterator.constructor.js");
9
9
  require("core-js/modules/esnext.iterator.filter.js");
@@ -32,10 +32,10 @@ var _parseLiteralValue = require("./parseLiteralValue");
32
32
  var _startsQuery = require("./startsQuery");
33
33
  const createMatch = exports.createMatch = /^\s*create(?![a-z0-9_-])/i;
34
34
  const headMatch = /^\s*create\s+(if\s+not\s+exists\s+)?([a-z0-9_-]+)\s*\(/i;
35
- const nameSource = '[a-z0-9_-]+';
36
- const nameMatch = new RegExp(`^${nameSource}$`, 'i');
35
+ const nameSource = exports.nameSource = '[a-z0-9_-]+';
36
+ const nameMatch = exports.nameMatch = new RegExp(`^${nameSource}$`, 'i');
37
37
  const numberMatch = new RegExp(`^${_parseLiteralValue.numberSource}$`, 'i');
38
- const fieldMatch = new RegExp(`^(${nameSource})\\s+(${nameSource})((?:\\s[\\s\\S]*)?)$`, 'i');
38
+ const fieldMatch = exports.fieldMatch = new RegExp(`^(${nameSource})\\s+(${nameSource})((?:\\s[\\s\\S]*)?)$`, 'i');
39
39
  const primaryMatch = /^primary\s+key\s*\(([^()]*)\)$/i;
40
40
  const uniqueMatch = /^unique\s*\(([^()]*)\)$/i;
41
41
  const indexMatch = /^index\s*\(([^()]*)\)$/i;
@@ -48,10 +48,17 @@ const lookupPrefix = {
48
48
  index: 'index',
49
49
  foreign: 'fk'
50
50
  };
51
+ /**
52
+ * The index file name a key's index lives in, from its type and fields (as create names it).
53
+ * @param type
54
+ * @param fields
55
+ */
56
+ const keyLookup = (type, fields) => `${lookupPrefix[type]}_${fields.join('_')}.json`;
51
57
  /**
52
58
  * Show a piece of the (masked) query the way it was written, for an error message.
53
59
  * @param text
54
60
  */
61
+ exports.keyLookup = keyLookup;
55
62
  const shown = text => (0, _maskStrings.unmaskStrings)(text.trim(), '\'');
56
63
  /**
57
64
  * The position of the `)` closing the `(` at `open`, or -1. Strings and arrays are placeholders by now, so round
@@ -197,6 +204,7 @@ const parseField = found => {
197
204
  * One key, or null when the item isn't written as a key (so it's a field).
198
205
  * @param item
199
206
  */
207
+ exports.parseField = parseField;
200
208
  const parseKey = item => {
201
209
  let found = item.match(primaryMatch);
202
210
  if (found) {
@@ -254,7 +262,7 @@ const finishKeys = (definition, keys) => {
254
262
  if (missing) {
255
263
  throw new Error(`The ${key.type} key (${key.fields.join(', ')}) names "${missing}", which is not a field`);
256
264
  }
257
- const lookup = `${lookupPrefix[key.type]}_${key.fields.join('_')}.json`;
265
+ const lookup = keyLookup(key.type, key.fields);
258
266
  if (lookups.has(lookup)) {
259
267
  throw new Error(`The same ${key.type} key on "${key.fields.join(', ')}" is given twice`);
260
268
  }
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.parseCreate=exports.createMatch=void 0,require("core-js/modules/es.array.includes.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.filter.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.some.js"),require("core-js/modules/esnext.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js");var _typeCheck=require("../../introspect/policies/typeCheck"),_maskStrings=require("./maskStrings"),_parseLiteralValue=require("./parseLiteralValue"),_startsQuery=require("./startsQuery");const createMatch=exports.createMatch=/^\s*create(?![a-z0-9_-])/i,headMatch=/^\s*create\s+(if\s+not\s+exists\s+)?([a-z0-9_-]+)\s*\(/i,nameSource="[a-z0-9_-]+",nameMatch=new RegExp(`^${nameSource}$`,"i"),numberMatch=new RegExp(`^${_parseLiteralValue.numberSource}$`,"i"),fieldMatch=new RegExp(`^(${nameSource})\\s+(${nameSource})((?:\\s[\\s\\S]*)?)$`,"i"),primaryMatch=/^primary\s+key\s*\(([^()]*)\)$/i,uniqueMatch=/^unique\s*\(([^()]*)\)$/i,indexMatch=/^index\s*\(([^()]*)\)$/i,foreignMatch=new RegExp(`^foreign\\s+key\\s*\\(([^()]*)\\)\\s*references\\s+(${nameSource})\\s*\\(([^()]*)\\)$`,"i"),stringLiteral=new RegExp(`^${_maskStrings.placeholderSource}$`),arrayLiteral=new RegExp(`^${_maskStrings.arraySource}$`),lookupPrefix={primary:"pk",unique:"unique",index:"index",foreign:"fk"},shown=e=>(0,_maskStrings.unmaskStrings)(e.trim(),"'"),findClose=(e,r)=>{let s=0;for(let t=r;t<e.length;t++)if("("===e[t])s++;else if(")"===e[t]&&(s--,0===s))return t;return-1},splitItems=e=>{const r=[];let s=0,t=0;for(let i=0;i<e.length;i++)"("===e[i]?s++:")"===e[i]?s--:","===e[i]&&0===s&&(r.push(e.slice(t,i)),t=i+1);return r.push(e.slice(t)),r.map((e=>e.trim()))},parseNames=(e,r)=>{const s=e.split(",").map((e=>e.trim()));if(s.some((e=>!nameMatch.test(e))))throw new Error(`Expected field names separated by commas in: ${shown(r)}`);if(new Set(s).size!==s.length)throw new Error(`The same field is named twice in: ${shown(r)}`);return s},parseDefault=e=>{if(void 0===e)throw new Error("Missing a value after default");const r=e.match(stringLiteral);if(r)return(0,_maskStrings.fromPlaceholder)(r[1]);const s=e.match(arrayLiteral);if(s)return JSON.parse((0,_maskStrings.fromPlaceholder)(s[1]));if(numberMatch.test(e))return(0,_parseLiteralValue.parseNumber)(e);switch(e.toLowerCase()){case"null":return null;case"true":return!0;case"false":return!1}throw new Error(`Unrecognized default value: ${shown(e)}`)},parseField=e=>{const[r,s,t,i]=e,n=t.toLowerCase();if(!_typeCheck.fieldTypes.includes(n))throw new Error(`Unknown type "${t}" for field "${s}" (expected ${_typeCheck.fieldTypes.join(", ")})`);const o={name:s,type:n,optional:!1,autoGenerate:!1},a=new Set,l=e=>{if(a.has(e))throw new Error(`"${e}" is given twice for field "${s}"`);a.add(e)},u=""===i.trim()?[]:i.trim().split(/\s+/);for(let e=0;e<u.length;e++){const t=u[e].toLowerCase();if("optional"===t)l("optional"),o.optional=!0;else if("auto"===t)l("auto"),o.autoGenerate=!0;else if("null"===t)l("null"),o.nullable=!0;else if("not"===t&&"null"===(u[e+1]||"").toLowerCase())l("null"),o.nullable=!1,e++;else{if("default"!==t)throw new Error(`Unrecognized "${shown(u[e])}" for field "${s}" in: ${shown(r)}`);l("default"),o.useDefault=!0,o.defaultValue=parseDefault(u[++e])}}if(o.autoGenerate&&o.useDefault)throw new Error(`Field "${s}" can't be both auto and have a default`);if(o.autoGenerate&&"string"!==n)throw new Error(`Field "${s}" is auto, which generates a UUID, so its type must be string`);return o},parseKey=e=>{let r=e.match(primaryMatch);if(r)return{type:"primary",fields:parseNames(r[1],e)};if(r=e.match(uniqueMatch),r)return{type:"unique",fields:parseNames(r[1],e)};if(r=e.match(indexMatch),r)return{type:"index",fields:parseNames(r[1],e)};if(r=e.match(foreignMatch),r){const s=parseNames(r[1],e),t=parseNames(r[3],e);if(1!==s.length||1!==t.length)throw new Error(`Only single-field foreign keys are supported so far: ${shown(e)}`);return{type:"foreign",fields:s,references:[{fields:[`${r[2]}.${t[0]}`],lookup:`${r[2]}.json`}]}}return null},finishKeys=(e,r)=>{const s=e.map((e=>e.name));if(r.filter((e=>"primary"===e.type)).length>1)throw new Error("Only one primary key is allowed");const t=new Set;return r.map((e=>{const r=e.fields.find((e=>!s.includes(e)));if(r)throw new Error(`The ${e.type} key (${e.fields.join(", ")}) names "${r}", which is not a field`);const i=`${lookupPrefix[e.type]}_${e.fields.join("_")}.json`;if(t.has(i))throw new Error(`The same ${e.type} key on "${e.fields.join(", ")}" is given twice`);return t.add(i),Object.assign({type:e.type,fields:e.fields,lookup:i},e.references?{references:e.references}:{})}))},parseCreate=e=>{const r=e.match(headMatch);if(!r)throw new Error("Expected: create [if not exists] <name> (<fields and keys>)");const s=r[2];if("__RECORDS"===s)throw new Error("__RECORDS is reserved for the list of records");const t=r[0].length-1,i=findClose(e,t);if(-1===i)throw new Error("Missing ) at the end of the field list");const n=e.slice(i+1).trim();if(n){if(_startsQuery.startsQueryMatch.test(n))throw new Error(`Missing ; before: ${shown(n)}`);throw new Error(`Unrecognized query value: ${shown(n)}`)}const o=e.slice(t+1,i),a=[],l=[];if(o.trim())for(const e of splitItems(o)){if(!e)throw new Error("Empty item in the field list (an extra comma?)");const r=parseKey(e);if(r){l.push(r);continue}const s=e.match(fieldMatch);if(!s)throw new Error(`Expected a field (<name> <type> ...) or a key in: ${shown(e)}`);const t=parseField(s);if(a.some((e=>e.name===t.name)))throw new Error(`Field "${t.name}" is given twice`);a.push(t)}return{entity:s,create:{ifNotExists:Boolean(r[1]),definition:a,keys:finishKeys(a,l)}}};exports.parseCreate=parseCreate;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.parseField=exports.parseCreate=exports.nameSource=exports.nameMatch=exports.keyLookup=exports.fieldMatch=exports.createMatch=void 0,require("core-js/modules/es.array.includes.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.filter.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.some.js"),require("core-js/modules/esnext.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js");var _typeCheck=require("../../introspect/policies/typeCheck"),_maskStrings=require("./maskStrings"),_parseLiteralValue=require("./parseLiteralValue"),_startsQuery=require("./startsQuery");const createMatch=exports.createMatch=/^\s*create(?![a-z0-9_-])/i,headMatch=/^\s*create\s+(if\s+not\s+exists\s+)?([a-z0-9_-]+)\s*\(/i,nameSource=exports.nameSource="[a-z0-9_-]+",nameMatch=exports.nameMatch=new RegExp(`^${nameSource}$`,"i"),numberMatch=new RegExp(`^${_parseLiteralValue.numberSource}$`,"i"),fieldMatch=exports.fieldMatch=new RegExp(`^(${nameSource})\\s+(${nameSource})((?:\\s[\\s\\S]*)?)$`,"i"),primaryMatch=/^primary\s+key\s*\(([^()]*)\)$/i,uniqueMatch=/^unique\s*\(([^()]*)\)$/i,indexMatch=/^index\s*\(([^()]*)\)$/i,foreignMatch=new RegExp(`^foreign\\s+key\\s*\\(([^()]*)\\)\\s*references\\s+(${nameSource})\\s*\\(([^()]*)\\)$`,"i"),stringLiteral=new RegExp(`^${_maskStrings.placeholderSource}$`),arrayLiteral=new RegExp(`^${_maskStrings.arraySource}$`),lookupPrefix={primary:"pk",unique:"unique",index:"index",foreign:"fk"},keyLookup=(e,r)=>`${lookupPrefix[e]}_${r.join("_")}.json`;exports.keyLookup=keyLookup;const shown=e=>(0,_maskStrings.unmaskStrings)(e.trim(),"'"),findClose=(e,r)=>{let s=0;for(let t=r;t<e.length;t++)if("("===e[t])s++;else if(")"===e[t]&&(s--,0===s))return t;return-1},splitItems=e=>{const r=[];let s=0,t=0;for(let i=0;i<e.length;i++)"("===e[i]?s++:")"===e[i]?s--:","===e[i]&&0===s&&(r.push(e.slice(t,i)),t=i+1);return r.push(e.slice(t)),r.map((e=>e.trim()))},parseNames=(e,r)=>{const s=e.split(",").map((e=>e.trim()));if(s.some((e=>!nameMatch.test(e))))throw new Error(`Expected field names separated by commas in: ${shown(r)}`);if(new Set(s).size!==s.length)throw new Error(`The same field is named twice in: ${shown(r)}`);return s},parseDefault=e=>{if(void 0===e)throw new Error("Missing a value after default");const r=e.match(stringLiteral);if(r)return(0,_maskStrings.fromPlaceholder)(r[1]);const s=e.match(arrayLiteral);if(s)return JSON.parse((0,_maskStrings.fromPlaceholder)(s[1]));if(numberMatch.test(e))return(0,_parseLiteralValue.parseNumber)(e);switch(e.toLowerCase()){case"null":return null;case"true":return!0;case"false":return!1}throw new Error(`Unrecognized default value: ${shown(e)}`)},parseField=e=>{const[r,s,t,i]=e,o=t.toLowerCase();if(!_typeCheck.fieldTypes.includes(o))throw new Error(`Unknown type "${t}" for field "${s}" (expected ${_typeCheck.fieldTypes.join(", ")})`);const n={name:s,type:o,optional:!1,autoGenerate:!1},a=new Set,l=e=>{if(a.has(e))throw new Error(`"${e}" is given twice for field "${s}"`);a.add(e)},u=""===i.trim()?[]:i.trim().split(/\s+/);for(let e=0;e<u.length;e++){const t=u[e].toLowerCase();if("optional"===t)l("optional"),n.optional=!0;else if("auto"===t)l("auto"),n.autoGenerate=!0;else if("null"===t)l("null"),n.nullable=!0;else if("not"===t&&"null"===(u[e+1]||"").toLowerCase())l("null"),n.nullable=!1,e++;else{if("default"!==t)throw new Error(`Unrecognized "${shown(u[e])}" for field "${s}" in: ${shown(r)}`);l("default"),n.useDefault=!0,n.defaultValue=parseDefault(u[++e])}}if(n.autoGenerate&&n.useDefault)throw new Error(`Field "${s}" can't be both auto and have a default`);if(n.autoGenerate&&"string"!==o)throw new Error(`Field "${s}" is auto, which generates a UUID, so its type must be string`);return n};exports.parseField=parseField;const parseKey=e=>{let r=e.match(primaryMatch);if(r)return{type:"primary",fields:parseNames(r[1],e)};if(r=e.match(uniqueMatch),r)return{type:"unique",fields:parseNames(r[1],e)};if(r=e.match(indexMatch),r)return{type:"index",fields:parseNames(r[1],e)};if(r=e.match(foreignMatch),r){const s=parseNames(r[1],e),t=parseNames(r[3],e);if(1!==s.length||1!==t.length)throw new Error(`Only single-field foreign keys are supported so far: ${shown(e)}`);return{type:"foreign",fields:s,references:[{fields:[`${r[2]}.${t[0]}`],lookup:`${r[2]}.json`}]}}return null},finishKeys=(e,r)=>{const s=e.map((e=>e.name));if(r.filter((e=>"primary"===e.type)).length>1)throw new Error("Only one primary key is allowed");const t=new Set;return r.map((e=>{const r=e.fields.find((e=>!s.includes(e)));if(r)throw new Error(`The ${e.type} key (${e.fields.join(", ")}) names "${r}", which is not a field`);const i=keyLookup(e.type,e.fields);if(t.has(i))throw new Error(`The same ${e.type} key on "${e.fields.join(", ")}" is given twice`);return t.add(i),Object.assign({type:e.type,fields:e.fields,lookup:i},e.references?{references:e.references}:{})}))},parseCreate=e=>{const r=e.match(headMatch);if(!r)throw new Error("Expected: create [if not exists] <name> (<fields and keys>)");const s=r[2];if("__RECORDS"===s)throw new Error("__RECORDS is reserved for the list of records");const t=r[0].length-1,i=findClose(e,t);if(-1===i)throw new Error("Missing ) at the end of the field list");const o=e.slice(i+1).trim();if(o){if(_startsQuery.startsQueryMatch.test(o))throw new Error(`Missing ; before: ${shown(o)}`);throw new Error(`Unrecognized query value: ${shown(o)}`)}const n=e.slice(t+1,i),a=[],l=[];if(n.trim())for(const e of splitItems(n)){if(!e)throw new Error("Empty item in the field list (an extra comma?)");const r=parseKey(e);if(r){l.push(r);continue}const s=e.match(fieldMatch);if(!s)throw new Error(`Expected a field (<name> <type> ...) or a key in: ${shown(e)}`);const t=parseField(s);if(a.some((e=>e.name===t.name)))throw new Error(`Field "${t.name}" is given twice`);a.push(t)}return{entity:s,create:{ifNotExists:Boolean(r[1]),definition:a,keys:finishKeys(a,l)}}};exports.parseCreate=parseCreate;
@@ -0,0 +1,17 @@
1
+ export type ParsedDatabaseStatement = {
2
+ command: 'create database' | 'drop database' | 'use' | 'show databases';
3
+ entity: string;
4
+ ifExists: boolean;
5
+ ifNotExists: boolean;
6
+ };
7
+ /**
8
+ * The statements about databases rather than records. `create database (...)` and a bare `drop database` stay a record
9
+ * named "database": these need a database name after the keyword.
10
+ */
11
+ export declare const databaseStatementMatch: RegExp;
12
+ /**
13
+ * Read `create database [if not exists] <name>`, `drop database [if exists] <name>`, `use <name>` or `show databases`.
14
+ * @param query - The statement, with its strings and arrays masked (see maskStrings).
15
+ */
16
+ export declare const parseDatabaseStatement: (query: string) => ParsedDatabaseStatement;
17
+ //# sourceMappingURL=parseDatabaseStatement.d.ts.map
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.parseDatabaseStatement = exports.databaseStatementMatch = void 0;
7
+ var _maskStrings = require("./maskStrings");
8
+ var _startsQuery = require("./startsQuery");
9
+ /**
10
+ * The statements about databases rather than records. `create database (...)` and a bare `drop database` stay a record
11
+ * named "database": these need a database name after the keyword.
12
+ */
13
+ const databaseStatementMatch = exports.databaseStatementMatch = /^\s*(?:(?:create|drop)\s+database\s+(?!\()[a-z0-9_-]|show\s+databases(?![a-z0-9_-])|use(?![a-z0-9_.-]))/i;
14
+ const createMatch = /^\s*create\s+database\s+(if\s+not\s+exists\s+)?([a-z0-9_-]+)/i;
15
+ const dropMatch = /^\s*drop\s+database\s+(if\s+exists\s+)?([a-z0-9_-]+)/i;
16
+ const useMatch = /^\s*use\s+([a-z0-9_-]+)/i;
17
+ const showMatch = /^\s*show\s+databases/i;
18
+ /**
19
+ * Nothing may follow a database statement but the end of the query.
20
+ * @param rest
21
+ */
22
+ const assertNothingAfter = rest => {
23
+ const after = rest.trim();
24
+ if (!after) {
25
+ return;
26
+ }
27
+ const shown = (0, _maskStrings.unmaskStrings)(after, '\'');
28
+ if (_startsQuery.startsQueryMatch.test(after)) {
29
+ throw new Error(`Missing ; before: ${shown}`);
30
+ }
31
+ throw new Error(`Unrecognized query value: ${shown}`);
32
+ };
33
+ /**
34
+ * Read `create database [if not exists] <name>`, `drop database [if exists] <name>`, `use <name>` or `show databases`.
35
+ * @param query - The statement, with its strings and arrays masked (see maskStrings).
36
+ */
37
+ const parseDatabaseStatement = query => {
38
+ const statement = {
39
+ ifExists: false,
40
+ ifNotExists: false
41
+ };
42
+ let found = query.match(createMatch);
43
+ if (found) {
44
+ assertNothingAfter(query.slice(found[0].length));
45
+ return {
46
+ ...statement,
47
+ command: 'create database',
48
+ entity: found[2],
49
+ ifNotExists: Boolean(found[1])
50
+ };
51
+ }
52
+ found = query.match(dropMatch);
53
+ if (found) {
54
+ assertNothingAfter(query.slice(found[0].length));
55
+ return {
56
+ ...statement,
57
+ command: 'drop database',
58
+ entity: found[2],
59
+ ifExists: Boolean(found[1])
60
+ };
61
+ }
62
+ found = query.match(showMatch);
63
+ if (found) {
64
+ assertNothingAfter(query.slice(found[0].length));
65
+ return {
66
+ ...statement,
67
+ command: 'show databases',
68
+ entity: '__DATABASES'
69
+ };
70
+ }
71
+ found = query.match(useMatch);
72
+ if (!found) {
73
+ throw new Error('Expected: use <database>');
74
+ }
75
+ assertNothingAfter(query.slice(found[0].length));
76
+ return {
77
+ ...statement,
78
+ command: 'use',
79
+ entity: found[1]
80
+ };
81
+ };
82
+ exports.parseDatabaseStatement = parseDatabaseStatement;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.parseDatabaseStatement=exports.databaseStatementMatch=void 0;var _maskStrings=require("./maskStrings"),_startsQuery=require("./startsQuery");const databaseStatementMatch=exports.databaseStatementMatch=/^\s*(?:(?:create|drop)\s+database\s+(?!\()[a-z0-9_-]|show\s+databases(?![a-z0-9_-])|use(?![a-z0-9_.-]))/i,createMatch=/^\s*create\s+database\s+(if\s+not\s+exists\s+)?([a-z0-9_-]+)/i,dropMatch=/^\s*drop\s+database\s+(if\s+exists\s+)?([a-z0-9_-]+)/i,useMatch=/^\s*use\s+([a-z0-9_-]+)/i,showMatch=/^\s*show\s+databases/i,assertNothingAfter=t=>{const e=t.trim();if(!e)return;const s=(0,_maskStrings.unmaskStrings)(e,"'");if(_startsQuery.startsQueryMatch.test(e))throw new Error(`Missing ; before: ${s}`);throw new Error(`Unrecognized query value: ${s}`)},parseDatabaseStatement=t=>{const e={ifExists:!1,ifNotExists:!1};let s=t.match(createMatch);if(s)return assertNothingAfter(t.slice(s[0].length)),{...e,command:"create database",entity:s[2],ifNotExists:Boolean(s[1])};if(s=t.match(dropMatch),s)return assertNothingAfter(t.slice(s[0].length)),{...e,command:"drop database",entity:s[2],ifExists:Boolean(s[1])};if(s=t.match(showMatch),s)return assertNothingAfter(t.slice(s[0].length)),{...e,command:"show databases",entity:"__DATABASES"};if(s=t.match(useMatch),!s)throw new Error("Expected: use <database>");return assertNothingAfter(t.slice(s[0].length)),{...e,command:"use",entity:s[1]}};exports.parseDatabaseStatement=parseDatabaseStatement;
@@ -11,6 +11,7 @@ import { ParsedGroupBy } from './findGroupBy';
11
11
  import { ParsedLimit } from './findLimit';
12
12
  import { ParsedOffset } from './findOffset';
13
13
  import { ParsedCreate } from './parseCreate';
14
+ import { ParsedAlter } from './parseAlter';
14
15
  export type ParsedQuery = {
15
16
  command: ParsedCommand | undefined;
16
17
  entity: ParsedEntity | undefined;
@@ -26,6 +27,8 @@ export type ParsedQuery = {
26
27
  limit: ParsedLimit | undefined;
27
28
  offset: ParsedOffset | undefined;
28
29
  create: ParsedCreate | undefined;
30
+ alter?: ParsedAlter;
31
+ ifNotExists?: boolean;
29
32
  ifExists: boolean | undefined;
30
33
  };
31
34
  /**