json-fs-query 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.
- package/.browserslistrc +1 -0
- package/.nvmrc +1 -0
- package/LICENSE +674 -0
- package/MAIN.md +15 -0
- package/README.md +63 -0
- package/babel.config.js +3 -0
- package/build-tools.config.json +30 -0
- package/dist/data/introspect/create.d.ts +11 -0
- package/dist/data/introspect/create.js +45 -0
- package/dist/data/introspect/create.min.js +1 -0
- package/dist/data/introspect/definitions/field.d.ts +28 -0
- package/dist/data/introspect/definitions/field.js +36 -0
- package/dist/data/introspect/definitions/field.min.js +1 -0
- package/dist/data/introspect/definitions/key.d.ts +26 -0
- package/dist/data/introspect/definitions/key.js +53 -0
- package/dist/data/introspect/definitions/key.min.js +1 -0
- package/dist/data/introspect/definitions/keyReference.d.ts +18 -0
- package/dist/data/introspect/definitions/keyReference.js +20 -0
- package/dist/data/introspect/definitions/keyReference.min.js +1 -0
- package/dist/data/introspect/definitions/record.d.ts +26 -0
- package/dist/data/introspect/definitions/record.js +32 -0
- package/dist/data/introspect/definitions/record.min.js +1 -0
- package/dist/data/introspect/describe.d.ts +2 -0
- package/dist/data/introspect/describe.js +15 -0
- package/dist/data/introspect/describe.min.js +1 -0
- package/dist/data/introspect/drop.d.ts +2 -0
- package/dist/data/introspect/drop.js +37 -0
- package/dist/data/introspect/drop.min.js +1 -0
- package/dist/data/introspect/index.d.ts +8 -0
- package/dist/data/introspect/index.js +30 -0
- package/dist/data/introspect/index.min.js +1 -0
- package/dist/data/introspect/policies/foreignKeyExists.d.ts +9 -0
- package/dist/data/introspect/policies/foreignKeyExists.js +25 -0
- package/dist/data/introspect/policies/foreignKeyExists.min.js +1 -0
- package/dist/data/introspect/policies/keyGenerate.d.ts +2 -0
- package/dist/data/introspect/policies/keyGenerate.js +17 -0
- package/dist/data/introspect/policies/keyGenerate.min.js +1 -0
- package/dist/data/introspect/policies/keyUnique.d.ts +2 -0
- package/dist/data/introspect/policies/keyUnique.js +21 -0
- package/dist/data/introspect/policies/keyUnique.min.js +1 -0
- package/dist/data/introspect/policies/typeCheck.d.ts +12 -0
- package/dist/data/introspect/policies/typeCheck.js +57 -0
- package/dist/data/introspect/policies/typeCheck.min.js +1 -0
- package/dist/data/introspect/policies/typeEnum.d.ts +2 -0
- package/dist/data/introspect/policies/typeEnum.js +14 -0
- package/dist/data/introspect/policies/typeEnum.min.js +1 -0
- package/dist/data/introspect/policies/useDefault.d.ts +2 -0
- package/dist/data/introspect/policies/useDefault.js +13 -0
- package/dist/data/introspect/policies/useDefault.min.js +1 -0
- package/dist/data/queries/deleteEntity.d.ts +2 -0
- package/dist/data/queries/deleteEntity.js +79 -0
- package/dist/data/queries/deleteEntity.min.js +1 -0
- package/dist/data/queries/groupBy.d.ts +2 -0
- package/dist/data/queries/groupBy.js +22 -0
- package/dist/data/queries/groupBy.min.js +1 -0
- package/dist/data/queries/insertEntity.d.ts +2 -0
- package/dist/data/queries/insertEntity.js +115 -0
- package/dist/data/queries/insertEntity.min.js +1 -0
- package/dist/data/queries/joinEntity.d.ts +2 -0
- package/dist/data/queries/joinEntity.js +27 -0
- package/dist/data/queries/joinEntity.min.js +1 -0
- package/dist/data/queries/mergeJoins.d.ts +2 -0
- package/dist/data/queries/mergeJoins.js +19 -0
- package/dist/data/queries/mergeJoins.min.js +1 -0
- package/dist/data/queries/parser.d.ts +2 -0
- package/dist/data/queries/parser.js +30 -0
- package/dist/data/queries/parser.min.js +1 -0
- package/dist/data/queries/query.d.ts +2 -0
- package/dist/data/queries/query.js +18 -0
- package/dist/data/queries/query.min.js +1 -0
- package/dist/data/queries/readEntity.d.ts +2 -0
- package/dist/data/queries/readEntity.js +14 -0
- package/dist/data/queries/readEntity.min.js +1 -0
- package/dist/data/queries/sortBy.d.ts +2 -0
- package/dist/data/queries/sortBy.js +25 -0
- package/dist/data/queries/sortBy.min.js +1 -0
- package/dist/data/queries/updateEntity.d.ts +2 -0
- package/dist/data/queries/updateEntity.js +164 -0
- package/dist/data/queries/updateEntity.min.js +1 -0
- package/dist/data/queries/where.d.ts +7 -0
- package/dist/data/queries/where.js +56 -0
- package/dist/data/queries/where.min.js +1 -0
- package/dist/data/utilities/deregisterRecord.d.ts +8 -0
- package/dist/data/utilities/deregisterRecord.js +34 -0
- package/dist/data/utilities/deregisterRecord.min.js +1 -0
- package/dist/data/utilities/getConditionIndexedList.d.ts +3 -0
- package/dist/data/utilities/getConditionIndexedList.js +47 -0
- package/dist/data/utilities/getConditionIndexedList.min.js +1 -0
- package/dist/data/utilities/getJoinIndexedList.d.ts +3 -0
- package/dist/data/utilities/getJoinIndexedList.js +59 -0
- package/dist/data/utilities/getJoinIndexedList.min.js +1 -0
- package/dist/data/utilities/getMatchingIndexes.d.ts +2 -0
- package/dist/data/utilities/getMatchingIndexes.js +28 -0
- package/dist/data/utilities/getMatchingIndexes.min.js +1 -0
- package/dist/data/utilities/matchIndexedJoins.d.ts +2 -0
- package/dist/data/utilities/matchIndexedJoins.js +61 -0
- package/dist/data/utilities/matchIndexedJoins.min.js +1 -0
- package/dist/data/utilities/mergeJoin.d.ts +2 -0
- package/dist/data/utilities/mergeJoin.js +44 -0
- package/dist/data/utilities/mergeJoin.min.js +1 -0
- package/dist/data/utilities/parsers/findCommand.d.ts +10 -0
- package/dist/data/utilities/parsers/findCommand.js +25 -0
- package/dist/data/utilities/parsers/findCommand.min.js +1 -0
- package/dist/data/utilities/parsers/findConditions.d.ts +15 -0
- package/dist/data/utilities/parsers/findConditions.js +28 -0
- package/dist/data/utilities/parsers/findConditions.min.js +1 -0
- package/dist/data/utilities/parsers/findGroupBy.d.ts +9 -0
- package/dist/data/utilities/parsers/findGroupBy.js +22 -0
- package/dist/data/utilities/parsers/findGroupBy.min.js +1 -0
- package/dist/data/utilities/parsers/findInsertValues.d.ts +13 -0
- package/dist/data/utilities/parsers/findInsertValues.js +34 -0
- package/dist/data/utilities/parsers/findInsertValues.min.js +1 -0
- package/dist/data/utilities/parsers/findJoinClauses.d.ts +13 -0
- package/dist/data/utilities/parsers/findJoinClauses.js +27 -0
- package/dist/data/utilities/parsers/findJoinClauses.min.js +1 -0
- package/dist/data/utilities/parsers/findJoinEntity.d.ts +9 -0
- package/dist/data/utilities/parsers/findJoinEntity.js +22 -0
- package/dist/data/utilities/parsers/findJoinEntity.min.js +1 -0
- package/dist/data/utilities/parsers/findLimit.d.ts +9 -0
- package/dist/data/utilities/parsers/findLimit.js +22 -0
- package/dist/data/utilities/parsers/findLimit.min.js +1 -0
- package/dist/data/utilities/parsers/findMergeJoins.d.ts +12 -0
- package/dist/data/utilities/parsers/findMergeJoins.js +26 -0
- package/dist/data/utilities/parsers/findMergeJoins.min.js +1 -0
- package/dist/data/utilities/parsers/findOffset.d.ts +9 -0
- package/dist/data/utilities/parsers/findOffset.js +22 -0
- package/dist/data/utilities/parsers/findOffset.min.js +1 -0
- package/dist/data/utilities/parsers/findSelects.d.ts +12 -0
- package/dist/data/utilities/parsers/findSelects.js +30 -0
- package/dist/data/utilities/parsers/findSelects.min.js +1 -0
- package/dist/data/utilities/parsers/findSortClauses.d.ts +12 -0
- package/dist/data/utilities/parsers/findSortClauses.js +30 -0
- package/dist/data/utilities/parsers/findSortClauses.min.js +1 -0
- package/dist/data/utilities/parsers/findUpdateValues.d.ts +15 -0
- package/dist/data/utilities/parsers/findUpdateValues.js +34 -0
- package/dist/data/utilities/parsers/findUpdateValues.min.js +1 -0
- package/dist/data/utilities/parsers/isEntityProperty.d.ts +2 -0
- package/dist/data/utilities/parsers/isEntityProperty.js +8 -0
- package/dist/data/utilities/parsers/isEntityProperty.min.js +1 -0
- package/dist/data/utilities/parsers/parseLiteralValue.d.ts +12 -0
- package/dist/data/utilities/parsers/parseLiteralValue.js +34 -0
- package/dist/data/utilities/parsers/parseLiteralValue.min.js +1 -0
- package/dist/data/utilities/parsers/parseQuery.d.ts +33 -0
- package/dist/data/utilities/parsers/parseQuery.js +51 -0
- package/dist/data/utilities/parsers/parseQuery.min.js +1 -0
- package/dist/data/utilities/parsers/removeFromQuery.d.ts +2 -0
- package/dist/data/utilities/parsers/removeFromQuery.js +8 -0
- package/dist/data/utilities/parsers/removeFromQuery.min.js +1 -0
- package/dist/data/utilities/parsers/splitEntityProperty.d.ts +5 -0
- package/dist/data/utilities/parsers/splitEntityProperty.js +23 -0
- package/dist/data/utilities/parsers/splitEntityProperty.min.js +1 -0
- package/dist/data/utilities/parsers/splitQueries.d.ts +2 -0
- package/dist/data/utilities/parsers/splitQueries.js +11 -0
- package/dist/data/utilities/parsers/splitQueries.min.js +1 -0
- package/dist/data/utilities/reconcileJoin.d.ts +2 -0
- package/dist/data/utilities/reconcileJoin.js +26 -0
- package/dist/data/utilities/reconcileJoin.min.js +1 -0
- package/dist/data/utilities/reconcileJoins.d.ts +2 -0
- package/dist/data/utilities/reconcileJoins.js +27 -0
- package/dist/data/utilities/reconcileJoins.min.js +1 -0
- package/dist/data/utilities/registerRecord.d.ts +8 -0
- package/dist/data/utilities/registerRecord.js +67 -0
- package/dist/data/utilities/registerRecord.min.js +1 -0
- package/dist/data/utilities/removeFromIndex.d.ts +14 -0
- package/dist/data/utilities/removeFromIndex.js +46 -0
- package/dist/data/utilities/removeFromIndex.min.js +1 -0
- package/dist/data/utilities/restrictIndexedList.d.ts +2 -0
- package/dist/data/utilities/restrictIndexedList.js +45 -0
- package/dist/data/utilities/restrictIndexedList.min.js +1 -0
- package/dist/data/utilities/retrieveFile.d.ts +2 -0
- package/dist/data/utilities/retrieveFile.js +28 -0
- package/dist/data/utilities/retrieveFile.min.js +1 -0
- package/dist/data/utilities/retrieveIndexes.d.ts +2 -0
- package/dist/data/utilities/retrieveIndexes.js +24 -0
- package/dist/data/utilities/retrieveIndexes.min.js +1 -0
- package/dist/data/utilities/retrieveRecord.d.ts +2 -0
- package/dist/data/utilities/retrieveRecord.js +9 -0
- package/dist/data/utilities/retrieveRecord.min.js +1 -0
- package/dist/data/utilities/retrieveRecords.d.ts +2 -0
- package/dist/data/utilities/retrieveRecords.js +21 -0
- package/dist/data/utilities/retrieveRecords.min.js +1 -0
- package/dist/data/utilities/runQuery.d.ts +2 -0
- package/dist/data/utilities/runQuery.js +72 -0
- package/dist/data/utilities/runQuery.min.js +1 -0
- package/dist/data/utilities/updateIndex.d.ts +17 -0
- package/dist/data/utilities/updateIndex.js +56 -0
- package/dist/data/utilities/updateIndex.min.js +1 -0
- package/dist/data/utilities/useJoinClause.d.ts +6 -0
- package/dist/data/utilities/useJoinClause.js +51 -0
- package/dist/data/utilities/useJoinClause.min.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +35 -0
- package/dist/index.min.js +1 -0
- package/gulpfile.mjs +15 -0
- package/package.json +44 -0
- package/src/data/introspect/create.test.js +67 -0
- package/src/data/introspect/create.ts +29 -0
- package/src/data/introspect/definitions/field.test.js +23 -0
- package/src/data/introspect/definitions/field.ts +48 -0
- package/src/data/introspect/definitions/key.test.js +100 -0
- package/src/data/introspect/definitions/key.ts +64 -0
- package/src/data/introspect/definitions/keyReference.test.js +8 -0
- package/src/data/introspect/definitions/keyReference.ts +23 -0
- package/src/data/introspect/definitions/record.ts +42 -0
- package/src/data/introspect/describe.test.js +36 -0
- package/src/data/introspect/describe.ts +9 -0
- package/src/data/introspect/drop.test.js +77 -0
- package/src/data/introspect/drop.ts +20 -0
- package/src/data/introspect/index.ts +8 -0
- package/src/data/introspect/policies/foreignKeyExists.test.js +44 -0
- package/src/data/introspect/policies/foreignKeyExists.ts +15 -0
- package/src/data/introspect/policies/keyGenerate.ts +11 -0
- package/src/data/introspect/policies/keyUnique.ts +11 -0
- package/src/data/introspect/policies/typeCheck.test.js +84 -0
- package/src/data/introspect/policies/typeCheck.ts +49 -0
- package/src/data/introspect/policies/typeEnum.ts +6 -0
- package/src/data/introspect/policies/useDefault.ts +6 -0
- package/src/data/queries/deleteEntity.test.js +86 -0
- package/src/data/queries/deleteEntity.ts +56 -0
- package/src/data/queries/groupBy.test.js +135 -0
- package/src/data/queries/groupBy.ts +16 -0
- package/src/data/queries/insertEntity.test.js +156 -0
- package/src/data/queries/insertEntity.ts +113 -0
- package/src/data/queries/joinEntity.test.js +109 -0
- package/src/data/queries/joinEntity.ts +20 -0
- package/src/data/queries/mergeJoins.test.js +174 -0
- package/src/data/queries/mergeJoins.ts +13 -0
- package/src/data/queries/parser.test.js +343 -0
- package/src/data/queries/parser.ts +24 -0
- package/src/data/queries/query.test.js +233 -0
- package/src/data/queries/query.ts +12 -0
- package/src/data/queries/readEntity.test.js +85 -0
- package/src/data/queries/readEntity.ts +8 -0
- package/src/data/queries/sortBy.test.js +131 -0
- package/src/data/queries/sortBy.ts +18 -0
- package/src/data/queries/updateEntity.test.js +228 -0
- package/src/data/queries/updateEntity.ts +162 -0
- package/src/data/queries/where.test.js +129 -0
- package/src/data/queries/where.ts +54 -0
- package/src/data/utilities/deregisterRecord.test.js +26 -0
- package/src/data/utilities/deregisterRecord.ts +24 -0
- package/src/data/utilities/getConditionIndexedList.ts +43 -0
- package/src/data/utilities/getJoinIndexedList.ts +53 -0
- package/src/data/utilities/getMatchingIndexes.ts +21 -0
- package/src/data/utilities/matchIndexedJoins.ts +45 -0
- package/src/data/utilities/mergeJoin.test.js +102 -0
- package/src/data/utilities/mergeJoin.ts +28 -0
- package/src/data/utilities/parsers/findCommand.ts +25 -0
- package/src/data/utilities/parsers/findConditions.test.js +38 -0
- package/src/data/utilities/parsers/findConditions.ts +32 -0
- package/src/data/utilities/parsers/findGroupBy.ts +20 -0
- package/src/data/utilities/parsers/findInsertValues.test.js +26 -0
- package/src/data/utilities/parsers/findInsertValues.ts +32 -0
- package/src/data/utilities/parsers/findJoinClauses.ts +29 -0
- package/src/data/utilities/parsers/findJoinEntity.ts +20 -0
- package/src/data/utilities/parsers/findLimit.ts +20 -0
- package/src/data/utilities/parsers/findMergeJoins.ts +27 -0
- package/src/data/utilities/parsers/findOffset.ts +20 -0
- package/src/data/utilities/parsers/findSelects.ts +31 -0
- package/src/data/utilities/parsers/findSortClauses.ts +31 -0
- package/src/data/utilities/parsers/findUpdateValues.test.js +37 -0
- package/src/data/utilities/parsers/findUpdateValues.ts +32 -0
- package/src/data/utilities/parsers/isEntityProperty.ts +1 -0
- package/src/data/utilities/parsers/parseLiteralValue.test.js +41 -0
- package/src/data/utilities/parsers/parseLiteralValue.ts +29 -0
- package/src/data/utilities/parsers/parseQuery.ts +73 -0
- package/src/data/utilities/parsers/removeFromQuery.ts +1 -0
- package/src/data/utilities/parsers/splitEntityProperty.test.js +8 -0
- package/src/data/utilities/parsers/splitEntityProperty.ts +17 -0
- package/src/data/utilities/parsers/splitQueries.test.js +33 -0
- package/src/data/utilities/parsers/splitQueries.ts +4 -0
- package/src/data/utilities/reconcileJoin.test.js +38 -0
- package/src/data/utilities/reconcileJoin.ts +23 -0
- package/src/data/utilities/reconcileJoins.test.js +160 -0
- package/src/data/utilities/reconcileJoins.ts +15 -0
- package/src/data/utilities/registerRecord.test.js +44 -0
- package/src/data/utilities/registerRecord.ts +44 -0
- package/src/data/utilities/removeFromIndex.test.js +44 -0
- package/src/data/utilities/removeFromIndex.ts +34 -0
- package/src/data/utilities/restrictIndexedList.ts +38 -0
- package/src/data/utilities/retrieveFile.test.js +49 -0
- package/src/data/utilities/retrieveFile.ts +19 -0
- package/src/data/utilities/retrieveIndexes.ts +17 -0
- package/src/data/utilities/retrieveRecord.ts +3 -0
- package/src/data/utilities/retrieveRecords.test.js +223 -0
- package/src/data/utilities/retrieveRecords.ts +16 -0
- package/src/data/utilities/runQuery.test.js +88 -0
- package/src/data/utilities/runQuery.ts +75 -0
- package/src/data/utilities/updateIndex.test.js +44 -0
- package/src/data/utilities/updateIndex.ts +45 -0
- package/src/data/utilities/useJoinClause.test.js +94 -0
- package/src/data/utilities/useJoinClause.ts +44 -0
- package/src/global.d.ts +1 -0
- package/src/index.test.js +52 -0
- package/src/index.ts +17 -0
- package/test-data/__RECORDS.json +32 -0
- package/test-data/__indexes/exercise_muscle/fk_exercise_id.json +14 -0
- package/test-data/__indexes/exercise_muscle/fk_muscle_id.json +14 -0
- package/test-data/__indexes/exercise_muscle/pk__id.json +14 -0
- package/test-data/__indexes/exercises/pk__id.json +14 -0
- package/test-data/__indexes/muscles/pk__id.json +58 -0
- package/test-data/__indexes/workouts/fk_exercise_id.json +23 -0
- package/test-data/__indexes/workouts/index_date.json +18 -0
- package/test-data/__indexes/workouts/pk__id.json +26 -0
- package/test-data/copyDatabase.ts +64 -0
- package/test-data/exercise_muscle/bench_press_pectorals.json +6 -0
- package/test-data/exercise_muscle/deadlift_hamstrings.json +6 -0
- package/test-data/exercise_muscle/squat_quadriceps.json +6 -0
- package/test-data/exercise_muscle.json +69 -0
- package/test-data/exercises/bench_press.json +5 -0
- package/test-data/exercises/deadlift.json +5 -0
- package/test-data/exercises/squat.json +5 -0
- package/test-data/exercises.json +34 -0
- package/test-data/muscles/abdominal.json +6 -0
- package/test-data/muscles/biceps.json +6 -0
- package/test-data/muscles/calves.json +6 -0
- package/test-data/muscles/deltoids.json +6 -0
- package/test-data/muscles/erector_spinae.json +6 -0
- package/test-data/muscles/forearms.json +6 -0
- package/test-data/muscles/gluteus_maximus.json +6 -0
- package/test-data/muscles/hamstrings.json +6 -0
- package/test-data/muscles/latissimus_dorsi.json +6 -0
- package/test-data/muscles/obliques.json +6 -0
- package/test-data/muscles/pectorals.json +6 -0
- package/test-data/muscles/quadriceps.json +6 -0
- package/test-data/muscles/trapezius.json +6 -0
- package/test-data/muscles/triceps.json +6 -0
- package/test-data/muscles.json +50 -0
- package/test-data/workouts/2024-06-02-001.json +10 -0
- package/test-data/workouts/2024-06-02-002.json +10 -0
- package/test-data/workouts/2024-06-02-003.json +10 -0
- package/test-data/workouts/2024-06-05-001.json +10 -0
- package/test-data/workouts/2024-06-05-002.json +10 -0
- package/test-data/workouts/2024-06-05-003.json +10 -0
- package/test-data/workouts.json +84 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const likeMatch = (dataValue, pattern) => {
|
|
2
|
+
if (typeof dataValue !== 'string' || typeof pattern !== 'string') {
|
|
3
|
+
return false
|
|
4
|
+
}
|
|
5
|
+
const regexPattern = pattern
|
|
6
|
+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
7
|
+
.replace(/%/g, '.*')
|
|
8
|
+
.replace(/_/g, '.')
|
|
9
|
+
return new RegExp(`^${regexPattern}$`, 'i').test(dataValue)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const makeCondition = (data, property, comparator, value) => {
|
|
13
|
+
if (!data.hasOwnProperty(property)) {
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
const dataValue = data[property]
|
|
17
|
+
switch (comparator) {
|
|
18
|
+
case '=':
|
|
19
|
+
return dataValue == value
|
|
20
|
+
case '!=':
|
|
21
|
+
case '<>':
|
|
22
|
+
return dataValue != value
|
|
23
|
+
case '>':
|
|
24
|
+
return dataValue > value
|
|
25
|
+
case '>=':
|
|
26
|
+
return dataValue >= value
|
|
27
|
+
case '<':
|
|
28
|
+
return dataValue < value
|
|
29
|
+
case '<=':
|
|
30
|
+
return dataValue <= value
|
|
31
|
+
case 'in':
|
|
32
|
+
return Array.isArray(value) && value.includes(dataValue)
|
|
33
|
+
case 'between':
|
|
34
|
+
return Array.isArray(value) && value.length === 2 && dataValue >= value[0] && dataValue <= value[1]
|
|
35
|
+
case 'like':
|
|
36
|
+
return likeMatch(dataValue, value)
|
|
37
|
+
}
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const where = (
|
|
42
|
+
dataSet = [],
|
|
43
|
+
{
|
|
44
|
+
property = '',
|
|
45
|
+
joinEntity = undefined,
|
|
46
|
+
comparator = '=',
|
|
47
|
+
value = null
|
|
48
|
+
} = {}
|
|
49
|
+
) => {
|
|
50
|
+
return dataSet.filter((data) => {
|
|
51
|
+
const testProperty = joinEntity ? `${joinEntity}.${property}` : property
|
|
52
|
+
return makeCondition(data, testProperty, comparator, value)
|
|
53
|
+
})
|
|
54
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { registerRecord } from './registerRecord'
|
|
2
|
+
import { deregisterRecord } from './deregisterRecord'
|
|
3
|
+
import { testHelpers } from 'js-build-tools/testHelpers'
|
|
4
|
+
|
|
5
|
+
const databasePath = 'deregister-record-database/'
|
|
6
|
+
const gulpConfig = testHelpers.gulpConfig
|
|
7
|
+
gulpConfig.set('databasePath', databasePath)
|
|
8
|
+
testHelpers.setDefaults(databasePath)
|
|
9
|
+
|
|
10
|
+
beforeEach(testHelpers.beforeEach)
|
|
11
|
+
|
|
12
|
+
afterEach(testHelpers.afterEach)
|
|
13
|
+
|
|
14
|
+
describe('deregisterRecord', () => {
|
|
15
|
+
test('does nothing when the registry does not exist yet', async () => {
|
|
16
|
+
const result = await deregisterRecord('foo')
|
|
17
|
+
expect(result).toBeNull()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('removes a record from the registry, keeping other entries', async () => {
|
|
21
|
+
await registerRecord('foo')
|
|
22
|
+
await registerRecord('bar')
|
|
23
|
+
const result = await deregisterRecord('foo')
|
|
24
|
+
expect(result.entries).toEqual(['bar.json'])
|
|
25
|
+
})
|
|
26
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { recordDefinition } from '../introspect/definitions/record'
|
|
2
|
+
|
|
3
|
+
const gulpConfig = require('js-build-tools/gulp.config')
|
|
4
|
+
const { readFile, writeFile } = require('fs/promises')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Remove a record's filename from the __RECORDS registry. A missing registry file is left
|
|
8
|
+
* untouched rather than created, since there's nothing to remove from it.
|
|
9
|
+
* @param recordName
|
|
10
|
+
*/
|
|
11
|
+
export const deregisterRecord = async (recordName: string): Promise<recordDefinition | null> => {
|
|
12
|
+
const databasePath = gulpConfig.get('databasePath', 'database/')
|
|
13
|
+
const registryPath = `${databasePath}__RECORDS.json`
|
|
14
|
+
let registry: recordDefinition
|
|
15
|
+
try {
|
|
16
|
+
registry = JSON.parse(await readFile(registryPath, 'utf8'))
|
|
17
|
+
} catch (err) {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
const fileName = `${recordName}.json`
|
|
21
|
+
registry.entries = registry.entries.filter(entry => entry !== fileName)
|
|
22
|
+
await writeFile(registryPath, JSON.stringify(registry, null, 2))
|
|
23
|
+
return registry
|
|
24
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { isEntityProperty } from './parsers/isEntityProperty'
|
|
2
|
+
import { splitEntityProperty } from './parsers/splitEntityProperty'
|
|
3
|
+
import { retrieveRecord } from './retrieveRecord'
|
|
4
|
+
import { where } from '../queries/where'
|
|
5
|
+
import { retrieveFile } from './retrieveFile'
|
|
6
|
+
import { ParsedQuery } from './parsers/parseQuery'
|
|
7
|
+
import { getMatchingIndexes } from './getMatchingIndexes'
|
|
8
|
+
|
|
9
|
+
export const getConditionIndexedList = async (parsed: ParsedQuery, recordData, dataSet) => {
|
|
10
|
+
let reducedConditions = []
|
|
11
|
+
for (let condition of parsed.conditions) {
|
|
12
|
+
let entity = parsed.entity
|
|
13
|
+
let property = condition.property
|
|
14
|
+
if (isEntityProperty(property)) {
|
|
15
|
+
const entityProperty = splitEntityProperty(property)
|
|
16
|
+
entity = entityProperty.entity
|
|
17
|
+
property = entityProperty.property
|
|
18
|
+
}
|
|
19
|
+
if (!recordData.hasOwnProperty(entity)) {
|
|
20
|
+
recordData[entity] = await retrieveRecord(entity)
|
|
21
|
+
}
|
|
22
|
+
const indexed = {}
|
|
23
|
+
const retrieveMatchedFiles = async (entity, property, recordData, condition, dataSet, indexed) => {
|
|
24
|
+
const matchedRecords = where(indexed[entity][property], {
|
|
25
|
+
property: 'value',
|
|
26
|
+
comparator: condition.comparator,
|
|
27
|
+
value: condition.value
|
|
28
|
+
})
|
|
29
|
+
for (let matchedRecord of matchedRecords) {
|
|
30
|
+
for (let file of matchedRecord.record) {
|
|
31
|
+
dataSet[entity].push(await retrieveFile(`${entity}/${file}`))
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
let isIndexed = await getMatchingIndexes(entity, property, recordData, condition, dataSet, indexed, retrieveMatchedFiles)
|
|
36
|
+
if (!isIndexed) {
|
|
37
|
+
reducedConditions.push(condition)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
parsed.conditions = reducedConditions
|
|
41
|
+
return parsed
|
|
42
|
+
|
|
43
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import siFunciona from 'si-funciona'
|
|
2
|
+
import { splitEntityProperty } from './parsers/splitEntityProperty'
|
|
3
|
+
import { retrieveRecord } from './retrieveRecord'
|
|
4
|
+
import { ParsedQuery } from './parsers/parseQuery'
|
|
5
|
+
import { getMatchingIndexes } from './getMatchingIndexes'
|
|
6
|
+
import { matchIndexedJoins } from './matchIndexedJoins'
|
|
7
|
+
import { restrictIndexedList } from './restrictIndexedList'
|
|
8
|
+
|
|
9
|
+
export const getJoinIndexedList = async (parsed: ParsedQuery, recordData, dataSets) => {
|
|
10
|
+
let reducedJoins = []
|
|
11
|
+
const indexed = {}
|
|
12
|
+
let firstIteration = true
|
|
13
|
+
const entity = parsed.entity
|
|
14
|
+
const joinEntity = parsed.joinEntity
|
|
15
|
+
const joinClauses = parsed.joinClauses
|
|
16
|
+
for (const anEntity in dataSets[entity]) {
|
|
17
|
+
const currentEntity = dataSets[entity][anEntity]
|
|
18
|
+
// Loop over each of the main entities
|
|
19
|
+
let filterJoins = {}
|
|
20
|
+
filterJoins[entity] = [siFunciona.cloneObject(currentEntity)]
|
|
21
|
+
for (let joinClause of joinClauses) {
|
|
22
|
+
let indexedClone = siFunciona.cloneObject(indexed)
|
|
23
|
+
const { entity: entityA, property: propertyA } = splitEntityProperty(joinClause.propertyA)
|
|
24
|
+
const { entity: entityB, property: propertyB } = splitEntityProperty(joinClause.propertyB)
|
|
25
|
+
|
|
26
|
+
// Get the record for the entities A and B, store on the recordData
|
|
27
|
+
if (!recordData.hasOwnProperty(entityA)) {
|
|
28
|
+
recordData[entityA] = await retrieveRecord(entityA)
|
|
29
|
+
}
|
|
30
|
+
if (!recordData.hasOwnProperty(entityB)) {
|
|
31
|
+
recordData[entityB] = await retrieveRecord(entityB)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
indexedClone = restrictIndexedList(joinClause, filterJoins, indexedClone)
|
|
35
|
+
|
|
36
|
+
const retrieveMatchedFiles = matchIndexedJoins(entity, indexedClone, dataSets)
|
|
37
|
+
|
|
38
|
+
// Check if there are matching keys for the join condition
|
|
39
|
+
const matchAResult = await getMatchingIndexes(entityA, propertyA, recordData, joinClause, filterJoins, indexed)
|
|
40
|
+
const matchBResult = await getMatchingIndexes(entityB, propertyB, recordData, joinClause, filterJoins, indexed, retrieveMatchedFiles)
|
|
41
|
+
|
|
42
|
+
const isIndexed = matchAResult || matchBResult
|
|
43
|
+
|
|
44
|
+
if (!isIndexed && firstIteration) {
|
|
45
|
+
reducedJoins.push(joinClause)
|
|
46
|
+
firstIteration = false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
dataSets[entity][anEntity][joinEntity] = filterJoins[joinEntity]
|
|
50
|
+
}
|
|
51
|
+
parsed.joinClauses = reducedJoins
|
|
52
|
+
return parsed
|
|
53
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { retrieveIndexes } from './retrieveIndexes'
|
|
2
|
+
|
|
3
|
+
export const getMatchingIndexes = async (entity: string, property: string, recordData: Object, clause: Object, dataSet: Object, indexed: Object, cb: Function = null): Promise<boolean> => {
|
|
4
|
+
let isIndexed = false
|
|
5
|
+
for (let key of recordData[entity].keys) {
|
|
6
|
+
if (key.fields.includes(property)) {
|
|
7
|
+
dataSet[entity] = []
|
|
8
|
+
if (!indexed.hasOwnProperty(entity)) {
|
|
9
|
+
indexed[entity] = {}
|
|
10
|
+
}
|
|
11
|
+
if (!indexed[entity].hasOwnProperty(property)) {
|
|
12
|
+
indexed[entity][property] = (await retrieveIndexes(entity, [property], recordData))[property]
|
|
13
|
+
}
|
|
14
|
+
if (typeof cb === 'function') {
|
|
15
|
+
await cb(entity, property, recordData, clause, dataSet, indexed)
|
|
16
|
+
}
|
|
17
|
+
isIndexed = true
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return isIndexed
|
|
21
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import siFunciona from 'si-funciona'
|
|
2
|
+
import { where } from '../queries/where'
|
|
3
|
+
import { retrieveFile } from './retrieveFile'
|
|
4
|
+
import { splitEntityProperty } from './parsers/splitEntityProperty'
|
|
5
|
+
|
|
6
|
+
export const matchIndexedJoins = (entity, indexedClone, dataSets) => async (focusEntity, property, recordData, joinClause, filterJoins, indexed) => {
|
|
7
|
+
const { entity: entityA, property: propertyA } = splitEntityProperty(joinClause.propertyA)
|
|
8
|
+
const { entity: entityB, property: propertyB } = splitEntityProperty(joinClause.propertyB)
|
|
9
|
+
const properties = [
|
|
10
|
+
{ entityFirst: entityA, propertyFirst: propertyA, entitySecond: entityB, propertySecond: propertyB },
|
|
11
|
+
{ entityFirst: entityB, propertyFirst: propertyB, entitySecond: entityA, propertySecond: propertyA },
|
|
12
|
+
]
|
|
13
|
+
for (let checkProp of properties) {
|
|
14
|
+
if (!indexedClone.hasOwnProperty(checkProp.entitySecond)) {
|
|
15
|
+
indexedClone[checkProp.entitySecond] = siFunciona.cloneObject(indexed[checkProp.entitySecond])
|
|
16
|
+
}
|
|
17
|
+
const matchedRecords = indexedClone[checkProp.entitySecond][checkProp.propertySecond].reduce(
|
|
18
|
+
(dataA, dataB) => siFunciona.mergeArrays(
|
|
19
|
+
dataA,
|
|
20
|
+
where(indexedClone[checkProp.entityFirst][checkProp.propertyFirst], {
|
|
21
|
+
property: 'value',
|
|
22
|
+
comparator: joinClause.comparator,
|
|
23
|
+
value: dataB.value
|
|
24
|
+
})
|
|
25
|
+
),
|
|
26
|
+
[]
|
|
27
|
+
)
|
|
28
|
+
let skipPush = checkProp.entityFirst === entity
|
|
29
|
+
if (!dataSets.hasOwnProperty(checkProp.entityFirst)) {
|
|
30
|
+
dataSets[checkProp.entityFirst] = []
|
|
31
|
+
}
|
|
32
|
+
for (let matchedRecord of matchedRecords) {
|
|
33
|
+
for (let file of matchedRecord.record) {
|
|
34
|
+
let entityRecord = file
|
|
35
|
+
if (typeof file === 'string') {
|
|
36
|
+
entityRecord = await retrieveFile(`${checkProp.entityFirst}/${file}`)
|
|
37
|
+
}
|
|
38
|
+
if (!skipPush) {
|
|
39
|
+
dataSets[checkProp.entityFirst].push(entityRecord)
|
|
40
|
+
}
|
|
41
|
+
filterJoins[checkProp.entityFirst].push(entityRecord)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { mergeJoin } from './mergeJoin'
|
|
2
|
+
import { testHelpers } from 'js-build-tools/testHelpers'
|
|
3
|
+
import { copyDatabase } from '../../../test-data/copyDatabase'
|
|
4
|
+
|
|
5
|
+
const databasePath = 'merge-join-database/'
|
|
6
|
+
const gulpConfig = testHelpers.gulpConfig
|
|
7
|
+
gulpConfig.set('databasePath', databasePath)
|
|
8
|
+
testHelpers.setDefaults(databasePath)
|
|
9
|
+
|
|
10
|
+
beforeEach(
|
|
11
|
+
() => testHelpers.beforeEach()
|
|
12
|
+
.then(() => copyDatabase(databasePath))
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
afterEach(testHelpers.afterEach)
|
|
16
|
+
|
|
17
|
+
describe('mergeJoin', () => {
|
|
18
|
+
test('can combine both entities', async () => {
|
|
19
|
+
const results = await mergeJoin('workouts.exercise_id', 'exercises._id')
|
|
20
|
+
expect(results).toEqual({
|
|
21
|
+
workouts: [
|
|
22
|
+
{
|
|
23
|
+
_id: 1,
|
|
24
|
+
exercise_id: 1,
|
|
25
|
+
weight_effort: '115lbs',
|
|
26
|
+
reps: 10,
|
|
27
|
+
sets: 3,
|
|
28
|
+
note: '',
|
|
29
|
+
order: 0,
|
|
30
|
+
date: '2024-06-02',
|
|
31
|
+
'exercises.name': 'Bench Press',
|
|
32
|
+
'exercises.description': ''
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
_id: 2,
|
|
36
|
+
exercise_id: 2,
|
|
37
|
+
weight_effort: '155lbs',
|
|
38
|
+
reps: 10,
|
|
39
|
+
sets: 3,
|
|
40
|
+
note: '',
|
|
41
|
+
order: 1,
|
|
42
|
+
date: '2024-06-02',
|
|
43
|
+
'exercises.name': 'Deadlift',
|
|
44
|
+
'exercises.description': ''
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
_id: 3,
|
|
48
|
+
exercise_id: 3,
|
|
49
|
+
weight_effort: '155lbs',
|
|
50
|
+
reps: 10,
|
|
51
|
+
sets: 3,
|
|
52
|
+
note: '',
|
|
53
|
+
order: 2,
|
|
54
|
+
date: '2024-06-02',
|
|
55
|
+
'exercises.name': 'Squat',
|
|
56
|
+
'exercises.description': ''
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
_id: 4,
|
|
60
|
+
exercise_id: 1,
|
|
61
|
+
weight_effort: '120lbs',
|
|
62
|
+
reps: 10,
|
|
63
|
+
sets: 3,
|
|
64
|
+
note: '',
|
|
65
|
+
order: 0,
|
|
66
|
+
date: '2024-06-05',
|
|
67
|
+
'exercises.name': 'Bench Press',
|
|
68
|
+
'exercises.description': ''
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
_id: 5,
|
|
72
|
+
exercise_id: 2,
|
|
73
|
+
weight_effort: '160lbs',
|
|
74
|
+
reps: 10,
|
|
75
|
+
sets: 3,
|
|
76
|
+
note: '',
|
|
77
|
+
order: 1,
|
|
78
|
+
date: '2024-06-05',
|
|
79
|
+
'exercises.name': 'Deadlift',
|
|
80
|
+
'exercises.description': ''
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
_id: 6,
|
|
84
|
+
exercise_id: 3,
|
|
85
|
+
weight_effort: '160lbs',
|
|
86
|
+
reps: 10,
|
|
87
|
+
sets: 3,
|
|
88
|
+
note: '',
|
|
89
|
+
order: 2,
|
|
90
|
+
date: '2024-06-05',
|
|
91
|
+
'exercises.name': 'Squat',
|
|
92
|
+
'exercises.description': ''
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
exercises: [
|
|
96
|
+
{ _id: 1, name: 'Bench Press', description: '' },
|
|
97
|
+
{ _id: 2, name: 'Deadlift', description: '' },
|
|
98
|
+
{ _id: 3, name: 'Squat', description: '' }
|
|
99
|
+
]
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readEntity } from '../queries/readEntity'
|
|
2
|
+
import { splitEntityProperty } from './parsers/splitEntityProperty'
|
|
3
|
+
import { where } from '../queries/where'
|
|
4
|
+
|
|
5
|
+
export const mergeJoin = async (propertyA = '', propertyB = '', dataSets = {}) => {
|
|
6
|
+
const { entity: entityA, property: propA } = splitEntityProperty(propertyA)
|
|
7
|
+
const { entity: entityB, property: propB } = splitEntityProperty(propertyB)
|
|
8
|
+
|
|
9
|
+
dataSets = await readEntity(entityA, dataSets)
|
|
10
|
+
dataSets = await readEntity(entityB, dataSets)
|
|
11
|
+
|
|
12
|
+
dataSets[entityA] = dataSets[entityA].map((dataA) => {
|
|
13
|
+
const matches = where(dataSets[entityB], { property: propB, comparator: '=', value: dataA[propA] })
|
|
14
|
+
if (!matches.length) {
|
|
15
|
+
return dataA
|
|
16
|
+
}
|
|
17
|
+
const mergeMatch = matches[0]
|
|
18
|
+
for (let prop in mergeMatch) {
|
|
19
|
+
if (prop === propB) {
|
|
20
|
+
// Skip the condition property since it is irrelevant
|
|
21
|
+
continue
|
|
22
|
+
}
|
|
23
|
+
dataA[`${entityB}.${prop}`] = mergeMatch[prop]
|
|
24
|
+
}
|
|
25
|
+
return dataA
|
|
26
|
+
})
|
|
27
|
+
return dataSets
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { removeFromQuery } from './removeFromQuery'
|
|
2
|
+
import { ParsedQuery } from './parseQuery'
|
|
3
|
+
|
|
4
|
+
const commandMatch: RegExp = /^(delete|insert|read|update)\s+([a-z0-9_-]+)[\w\n]*/i
|
|
5
|
+
|
|
6
|
+
export type ParsedCommand = string | 'delete' | 'insert' | 'read' | 'update'
|
|
7
|
+
|
|
8
|
+
export type ParsedEntity = string
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extract command clauses from the query.
|
|
12
|
+
* @param parsed
|
|
13
|
+
* @param query
|
|
14
|
+
*/
|
|
15
|
+
export const findCommand = (parsed: ParsedQuery, query: string): string => {
|
|
16
|
+
const commandFound: RegExpMatchArray = query.match(commandMatch)
|
|
17
|
+
parsed['command'] = undefined
|
|
18
|
+
parsed['entity'] = undefined
|
|
19
|
+
if (commandFound) {
|
|
20
|
+
parsed['command'] = commandFound[1]
|
|
21
|
+
parsed['entity'] = commandFound[2]
|
|
22
|
+
return removeFromQuery(commandFound[0], query)
|
|
23
|
+
}
|
|
24
|
+
return query
|
|
25
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { findConditions } from './findConditions'
|
|
2
|
+
|
|
3
|
+
const parse = (query) => {
|
|
4
|
+
const parsed = { conditions: [] }
|
|
5
|
+
const remaining = findConditions(parsed, query)
|
|
6
|
+
return { parsed, remaining }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe('findConditions', () => {
|
|
10
|
+
test('extracts a quoted string value', () => {
|
|
11
|
+
const { parsed, remaining } = parse("where date = '2024-06-02'")
|
|
12
|
+
expect(parsed.conditions).toEqual([{ property: 'date', comparator: '=', value: '2024-06-02' }])
|
|
13
|
+
expect(remaining).toBe('')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('extracts an unquoted number as a real number', () => {
|
|
17
|
+
const { parsed } = parse('where reps > 10')
|
|
18
|
+
expect(parsed.conditions).toEqual([{ property: 'reps', comparator: '>', value: 10 }])
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('extracts an array literal as a real array', () => {
|
|
22
|
+
const { parsed } = parse('where exercise_id in [1,2,3]')
|
|
23
|
+
expect(parsed.conditions).toEqual([{ property: 'exercise_id', comparator: 'in', value: [1, 2, 3] }])
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('extracts null as the real null value', () => {
|
|
27
|
+
const { parsed } = parse('where note = null')
|
|
28
|
+
expect(parsed.conditions).toEqual([{ property: 'note', comparator: '=', value: null }])
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('extracts true and false as real booleans', () => {
|
|
32
|
+
const { parsed } = parse('where active = true where archived = false')
|
|
33
|
+
expect(parsed.conditions).toEqual([
|
|
34
|
+
{ property: 'active', comparator: '=', value: true },
|
|
35
|
+
{ property: 'archived', comparator: '=', value: false }
|
|
36
|
+
])
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { removeFromQuery } from './removeFromQuery'
|
|
2
|
+
import { parseLiteralValue, LiteralValue } from './parseLiteralValue'
|
|
3
|
+
import { ParsedQuery } from './parseQuery'
|
|
4
|
+
|
|
5
|
+
const conditionMatch: RegExp = /(and|or)?\s*(where)\s+([a-z0-9_-]+\.?[a-z0-9_-]*)\s+(=|!=|>|<|>=|<=|in|between|like)\s+(["'](.*)['"]|([0-9]+)|(\[.*])|(null)|(true)|(false))/ig
|
|
6
|
+
|
|
7
|
+
export type ConditionValue = LiteralValue
|
|
8
|
+
|
|
9
|
+
export type ParsedCondition = {
|
|
10
|
+
property: string
|
|
11
|
+
comparator: string
|
|
12
|
+
value: ConditionValue
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extract condition clauses from the query.
|
|
17
|
+
* @param parsed
|
|
18
|
+
* @param query
|
|
19
|
+
*/
|
|
20
|
+
export const findConditions = (parsed: ParsedQuery, query: string): string => {
|
|
21
|
+
const conditionsFound: IterableIterator<RegExpMatchArray> = query.matchAll(conditionMatch)
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
for (let condition of conditionsFound) {
|
|
24
|
+
parsed['conditions'].push({
|
|
25
|
+
property: condition[3],
|
|
26
|
+
comparator: condition[4],
|
|
27
|
+
value: parseLiteralValue(condition, 6)
|
|
28
|
+
})
|
|
29
|
+
query = removeFromQuery(condition[0], query)
|
|
30
|
+
}
|
|
31
|
+
return query
|
|
32
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { removeFromQuery } from './removeFromQuery'
|
|
2
|
+
import { ParsedQuery } from './parseQuery'
|
|
3
|
+
|
|
4
|
+
const groupMatch: RegExp = /group\s+by\s+(,?[a-z0-9._-]+)+/i
|
|
5
|
+
|
|
6
|
+
export type ParsedGroupBy = string
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extract the group by clauses from the query.
|
|
10
|
+
* @param parsed
|
|
11
|
+
* @param query
|
|
12
|
+
*/
|
|
13
|
+
export const findGroupBy = (parsed: ParsedQuery, query: string): string => {
|
|
14
|
+
const groupFound: RegExpMatchArray = query.match(groupMatch)
|
|
15
|
+
if (groupFound) {
|
|
16
|
+
parsed['groupBy'] = groupFound ? groupFound[1] : undefined
|
|
17
|
+
return removeFromQuery(groupFound[0], query)
|
|
18
|
+
}
|
|
19
|
+
return query
|
|
20
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { findInsertValues } from './findInsertValues'
|
|
2
|
+
|
|
3
|
+
const parse = (query) => {
|
|
4
|
+
const parsed = {}
|
|
5
|
+
const remaining = findInsertValues(parsed, query)
|
|
6
|
+
return { parsed, remaining }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe('findInsertValues', () => {
|
|
10
|
+
test('extracts a single row, wrapped in a one-element array', () => {
|
|
11
|
+
const { parsed, remaining } = parse("insert workouts values reps = 12, date = '2024-06-02'")
|
|
12
|
+
expect(parsed.insertValues).toEqual([{ reps: 12, date: '2024-06-02' }])
|
|
13
|
+
expect(remaining).toBe('insert workouts')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('extracts every literal shape', () => {
|
|
17
|
+
const { parsed } = parse('values active = true, tags = [1,2,3], note = null')
|
|
18
|
+
expect(parsed.insertValues).toEqual([{ active: true, tags: [1, 2, 3], note: null }])
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('leaves the query untouched, with an empty insertValues, when there is no values clause', () => {
|
|
22
|
+
const { parsed, remaining } = parse("read workouts where date = '2024-06-02'")
|
|
23
|
+
expect(parsed.insertValues).toEqual([])
|
|
24
|
+
expect(remaining).toBe("read workouts where date = '2024-06-02'")
|
|
25
|
+
})
|
|
26
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { removeFromQuery } from './removeFromQuery'
|
|
2
|
+
import { parseLiteralValue } from './parseLiteralValue'
|
|
3
|
+
import { ParsedQuery } from './parseQuery'
|
|
4
|
+
|
|
5
|
+
const literal = '(?:["\'][^"\']*[\'"]|[0-9]+|\\[.*?]|null|true|false)'
|
|
6
|
+
const valuesClauseMatch: RegExp = new RegExp(`(values)\\s+([a-z0-9_-]+\\s*=\\s*${literal}(?:\\s*,\\s*[a-z0-9_-]+\\s*=\\s*${literal})*)`, 'i')
|
|
7
|
+
const assignmentMatch: RegExp = /([a-z0-9_-]+)\s*=\s*(["'](.*?)['"]|([0-9]+)|(\[.*?])|(null)|(true)|(false))/ig
|
|
8
|
+
|
|
9
|
+
export type ParsedInsertRow = { [field: string]: any }
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Extract an insert's "values field = value, field2 = value2" clause from the query, the same way
|
|
13
|
+
* findUpdateValues() does for "set". insertEntity() accepts an array of rows for bulk inserts, but
|
|
14
|
+
* a query string only describes a single row for now - wrapped in a one-element array to match.
|
|
15
|
+
* @param parsed
|
|
16
|
+
* @param query
|
|
17
|
+
*/
|
|
18
|
+
export const findInsertValues = (parsed: ParsedQuery, query: string): string => {
|
|
19
|
+
const clauseFound: RegExpMatchArray = query.match(valuesClauseMatch)
|
|
20
|
+
parsed['insertValues'] = []
|
|
21
|
+
if (!clauseFound) {
|
|
22
|
+
return query
|
|
23
|
+
}
|
|
24
|
+
const row = {}
|
|
25
|
+
const assignmentsFound: IterableIterator<RegExpMatchArray> = clauseFound[2].matchAll(assignmentMatch)
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
for (const assignment of assignmentsFound) {
|
|
28
|
+
row[assignment[1]] = parseLiteralValue(assignment, 3)
|
|
29
|
+
}
|
|
30
|
+
parsed['insertValues'] = [row]
|
|
31
|
+
return removeFromQuery(clauseFound[0], query)
|
|
32
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { removeFromQuery } from './removeFromQuery'
|
|
2
|
+
import { ParsedQuery } from './parseQuery'
|
|
3
|
+
|
|
4
|
+
const joinClauseMatch: RegExp = /(and)?\s*on\s+([a-z0-9_-]+\.[a-z0-9_-]+)\s+(=|!=|>|<|>=|<=|in|between|like)\s+([a-z0-9_-]+\.[a-z0-9_-]+)/ig
|
|
5
|
+
|
|
6
|
+
export type ParsedJoinClause = {
|
|
7
|
+
propertyA: string
|
|
8
|
+
comparator: string
|
|
9
|
+
propertyB: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extract condition clauses from the query.
|
|
14
|
+
* @param parsed
|
|
15
|
+
* @param query
|
|
16
|
+
*/
|
|
17
|
+
export const findJoinClauses = (parsed: ParsedQuery, query: string): string => {
|
|
18
|
+
const joinClausesFound: IterableIterator<RegExpMatchArray> = query.matchAll(joinClauseMatch)
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
for (let joinClause of joinClausesFound) {
|
|
21
|
+
parsed['joinClauses'].push({
|
|
22
|
+
propertyA: joinClause[2],
|
|
23
|
+
comparator: joinClause[3],
|
|
24
|
+
propertyB: joinClause[4]
|
|
25
|
+
})
|
|
26
|
+
query = removeFromQuery(joinClause[0], query)
|
|
27
|
+
}
|
|
28
|
+
return query
|
|
29
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { removeFromQuery } from './removeFromQuery'
|
|
2
|
+
import { ParsedQuery } from './parseQuery'
|
|
3
|
+
|
|
4
|
+
const joinEntityMatch: RegExp = /\.([a-z0-9_-]+)/i
|
|
5
|
+
|
|
6
|
+
export type ParsedJoinEntity = string
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extract the main join entity from the query.
|
|
10
|
+
* @param parsed
|
|
11
|
+
* @param query
|
|
12
|
+
*/
|
|
13
|
+
export const findJoinEntity = (parsed: ParsedQuery, query: string): string => {
|
|
14
|
+
const joinEntityFound: RegExpMatchArray = query.match(joinEntityMatch)
|
|
15
|
+
if (joinEntityFound) {
|
|
16
|
+
parsed['joinEntity'] = joinEntityFound ? joinEntityFound[1] : undefined
|
|
17
|
+
return removeFromQuery(joinEntityFound[0], query)
|
|
18
|
+
}
|
|
19
|
+
return query
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { removeFromQuery } from './removeFromQuery'
|
|
2
|
+
import { ParsedQuery } from './parseQuery'
|
|
3
|
+
|
|
4
|
+
const limitMatch: RegExp = /limit\s*(\d+)/i
|
|
5
|
+
|
|
6
|
+
export type ParsedLimit = number
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extract the limit clauses from the query.
|
|
10
|
+
* @param parsed
|
|
11
|
+
* @param query
|
|
12
|
+
*/
|
|
13
|
+
export const findLimit = (parsed: ParsedQuery, query: string): string => {
|
|
14
|
+
const limitFound: RegExpMatchArray = query.match(limitMatch)
|
|
15
|
+
if (limitFound) {
|
|
16
|
+
parsed['limit'] = limitFound ? parseInt(limitFound[1]) : undefined
|
|
17
|
+
return removeFromQuery(limitFound[0], query)
|
|
18
|
+
}
|
|
19
|
+
return query
|
|
20
|
+
}
|