telos-oql 1.0.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/LICENSE.md ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,264 @@
1
+ # OmniQuery
2
+
3
+ ## 1 - Abstract
4
+
5
+ ***The State of Union.***
6
+
7
+ OmniQuery (OQ, or OmniQuery Language - OQL / "Ockle") is a LISP dialect for database-agnostic
8
+ querying.
9
+
10
+ ## 2 - Contents
11
+
12
+ ### 2.1 - Language
13
+
14
+ #### 2.1.1 - Conventions
15
+
16
+ ##### 2.1.1.1 - Standard Fusion LISP
17
+
18
+ OmniQuery borrows the logic and arithmetic operators of
19
+ [Standard Fusion LISP](https://github.com/Telos-Project/Fusion-LISP?tab=readme-ov-file#222---standard-fusion-lisp).
20
+
21
+ Additionally, it also borrows the return operator, uses modified versions of certain list-related
22
+ operators, and uses the [dynamic LISP](https://github.com/Telos-Project/Fusion-LISP?tab=readme-ov-file#2222---dynamic-lisp)
23
+ conventions.
24
+
25
+ ##### 2.1.1.2 - Data
26
+
27
+ ###### 2.1.1.2.1 - State
28
+
29
+ OmniQuery is, in and of itself, stateless.
30
+
31
+ ###### 2.1.1.2.2 - Contexts
32
+
33
+ OmniQuery contexts are objects which act as cursors which point to specific databases and sets of
34
+ data therein.
35
+
36
+ ###### 2.1.1.2.3 - Dynamic Mapping
37
+
38
+ OmniQuery represents any database it interacts with, and the contexts it uses to interact with
39
+ them, as dynamic lists. The resulting mapping is referred to as a dynamic mapped database (DMDB).
40
+
41
+ As such, when a context is returned to an external environment, its contents are returned as a
42
+ dynamic list. Usually, the returned contents are in the form JSON.
43
+
44
+ A relational database may be mapped to a dynamic list, where each table in the database is
45
+ represented as a list dynamic value in said dynamic list, the key of the value being the name of
46
+ the table and the value itself being the contents of the table.
47
+
48
+ Each element of said list shall be a dynamic list representing a row in said table, with each value
49
+ of said list being a dynamic value and representing a column in said table, the key of the value
50
+ being the name of the column, and the value itself being the value of the column, converted to a
51
+ JSON compatible types.
52
+
53
+ ###### 2.1.1.2.4 - Type LISP
54
+
55
+ Type LISP is a LISP convention where a value may be assigned metadata using a list where the
56
+ operator is "type", where said value is the first argument, and where the second argument is a
57
+ dynamic list containing said metadata.
58
+
59
+ Codified conventions for the content or application of such metadata are referred to as type LISP
60
+ conventions.
61
+
62
+ ##### 2.1.1.3 - Usage
63
+
64
+ ###### 2.1.1.3.1 - Selectors
65
+
66
+ An OmniQuery script which returns data but does not edit data may be treated as a selector for the
67
+ returned data.
68
+
69
+ ###### 2.1.1.3.2 - Subscriptions
70
+
71
+ OmniQuery subscriptions event handlers tied to specific values within a DMDB which trigger events
72
+ when said values are altered, ideally with the previous and new values being passed to said event.
73
+
74
+ OmniQuery subscriptions may be specified with OmniQuery selectors.
75
+
76
+ ###### 2.1.1.3.3 - Entanglement
77
+
78
+ OmniQuery entanglement is when rules for two values in separate DMDBs are enforced to keep them
79
+ aligned, if not identical, when one of them is altered.
80
+
81
+ OmniQuery entanglements may be specified with OmniQuery selectors, handled by OmniQuery
82
+ subscriptions, and declared and unidirectional or bidirectional.
83
+
84
+ ###### 2.1.1.3.4 - Resolvers
85
+
86
+ An OmniQuery resolver is an API endpoint which transforms an incoming query to the API into a query
87
+ to a database according to its content.
88
+
89
+ ###### 2.1.1.3.5 - Streams
90
+
91
+ An OmniQuery stream is a sustained connection between data in a DMDB and an external system.
92
+
93
+ ###### 2.1.1.3.6 - Agnostic Scripts
94
+
95
+ Agnostic scripts are scripts for a system which may be written in any language. As such, rather
96
+ than interacting with said system through environmental variables and functions, the scripts,
97
+ written as function bodies, return an OmniQuery script as a LISP string, which executes upon the
98
+ state of said system (said state itself represented as a DMDB), the results of which are then
99
+ passed to the same script as a dynamic list encoded in a JSON string upon its next execution.
100
+
101
+ ##### 2.1.1.4 - Meta Models
102
+
103
+ Meta models are models which contextualize a disparate set of records and databases by serializing
104
+ them within, or referencing them from, a hierarchical structure.
105
+
106
+ ###### 2.1.1.4.1 - Dynamic Meta Models
107
+
108
+ A dynamic list, referred to as a model list, may be used to encode the structure of a meta model,
109
+ and metadata may be assigned to a model list by embedding it in a meta dynamic list referred to as
110
+ a meta model list.
111
+
112
+ The model list should only serialize the hierarchical structure and data content of the meta model
113
+ to which it maps. A model list is not required to serialize the entirety of the meta model to which
114
+ it, though one which does is referred to as a complete model list, and one which does not is
115
+ referred to as incomplete.
116
+
117
+ The property lists assigned to values within model lists may have a value with the key "context",
118
+ which contains an OQL query which returns a context that the value corresponding to the property
119
+ list in question acts as an alias to, with any descendant of said corresponding value being nested
120
+ within said context; and may have a value with the key "properties", said value being a dynamic
121
+ list by default, which specifies the system and type properties assigned to the value corresponding
122
+ to the property list in question.
123
+
124
+ ###### 2.1.1.5.2 - Dynamic OQL Queries
125
+
126
+ A dynamic OQL query is submitted as a meta model list where the property lists thereof may have an
127
+ additional value with the key "query", containing an OQL query to execute upon the resource to
128
+ which the value, corresponding to the property list in question, maps.
129
+
130
+ When executed, it shall not only execute said OQL queries, but shall also create and update the
131
+ resources to which it maps according to the structure and content declaratively specified in its
132
+ model list and property list properties. It shall then resolve to and return itself with the data
133
+ said OQL queries resolved to embedded in its model list and property list properties.
134
+
135
+ #### 2.1.2 - Operators
136
+
137
+ ##### 2.1.2.1 - arguments
138
+
139
+ The arguments atom, if used in the main scope of the script, resolves to a context passed in by the
140
+ process executing the script, and if used in the scope of an isolated expression, resolves to the
141
+ value passed to said expression when said expression is invoked.
142
+
143
+ ##### 2.1.2.2 - at
144
+
145
+ ###### 2.1.2.2.1 - As Context
146
+
147
+ The at operator takes a context object as its first argument, with each subsequent argument being a
148
+ number or string.
149
+
150
+ For each subsequent argument, it shall be treated as an identifier for a value in the dynamic list
151
+ selected corresponding to the previous argument, with strings being keys and numbers being indices.
152
+
153
+ It shall return a context object corresponding to the dynamic list selected by the last argument.
154
+
155
+ ###### 2.1.2.2.2 - As Field
156
+
157
+ If used in an expression applied to other values as part of a selection or transformation process,
158
+ the at operator shall operate largely as described above, but with the arguments atom as the first
159
+ argument, representing the value to which the expression is applied, and with the operator
160
+ returning the raw value selected by its last argument rather than a context.
161
+
162
+ ##### 2.1.2.3 - access
163
+
164
+ The access operator takes a string as its first argument, and may optionally take a dynamic list as
165
+ its second argument.
166
+
167
+ The string shall specify the address of a database to be accessed, and the dynamic list, if
168
+ present, shall specify credentials for accessing said database if necessary.
169
+
170
+ It shall return a context object representing the accessed database.
171
+
172
+ ##### 2.1.2.4 - append
173
+
174
+ The append operator takes a context object as its first argument, with each subsequent argument
175
+ being a dynamic list.
176
+
177
+ It shall create a value in the database, and at the location therein, corresponding to the context,
178
+ from each dynamic list.
179
+
180
+ ##### 2.1.2.5 - crop
181
+
182
+ The crop operator takes a context argument as its first argument, and a number as its second. It
183
+ may also optionally have a third numerical argument.
184
+
185
+ It shall return the context modified such that its contents are trimmed to no more than the length
186
+ specified by the first numerical argument. However, if it has a third argument, said contents shall
187
+ be cropped to a subsection starting at the index specified by the second numerical argument, before
188
+ the operation is otherwise applied as specified.
189
+
190
+ ##### 2.1.2.6 - focus
191
+
192
+ The focus operator takes a context argument as its first argument, with each subsequent argument
193
+ being a string.
194
+
195
+ It shall return the context modified such that all values nested within it only contain values
196
+ keyed by the specified strings.
197
+
198
+ ##### 2.1.2.7 - filter
199
+
200
+ The filter operator takes a context argument as its first argument, with an expression that
201
+ evaluates to a boolean as its second argument.
202
+
203
+ It shall return the context modified such that any value nested within it is removed if the
204
+ expression, when applied to it, resolves to false.
205
+
206
+ ##### 2.1.2.8 - merge
207
+
208
+ The merge operator takes two context objects, which should correspond to tables, as its first two
209
+ arguments, the first one being referred to as the left context and the second being referred to as
210
+ the right context, and an expression which resolves to a boolean as its third argument.
211
+
212
+ It returns a new context generated by performing a full outer join on the two contexts with the
213
+ expression as the join condition.
214
+
215
+ ###### 2.1.2.8.1 - merge-inner
216
+
217
+ The merge-inner operator behaves similarly to the merge operator, but performs an inner join
218
+ instead of a full outer join.
219
+
220
+ ###### 2.1.2.8.2 - merge-lateral
221
+
222
+ The merge-lateral operator behaves similarly to the merge operator, but performs a left outer join
223
+ instead of a full outer join.
224
+
225
+ ##### 2.1.2.9 - properties
226
+
227
+ The properties operator takes a context as its only argument, and returns a context containing the
228
+ system metadata corresponding to the original context. Such metadata shall, if applicable, include
229
+ data regarding the size of the latter context's content.
230
+
231
+ ##### 2.1.2.10 - query
232
+
233
+ The query operator takes a list containing an OQL query as its only argument, executes the query,
234
+ and returns the contents of any context object the query returns as a dynamic list.
235
+
236
+ It may be used to embed OQL in other LISP dialects.
237
+
238
+ ###### 2.1.2.10.1 - query-meta
239
+
240
+ The query-meta operator behaves similarly to the query operator, but instead takes a dynamic list
241
+ containing a dynamic OQL query as its only argument, executes the query, and returns the resulting
242
+ meta model the query resolves to as a dynamic list.
243
+
244
+ ##### 2.1.2.11 - remove
245
+
246
+ The remove operator takes a context object as its only argument, and removes all values which
247
+ correspond to the context from the database which contains them.
248
+
249
+ ##### 2.1.2.12 - set
250
+
251
+ The set operator takes a context object as its first argument, and an arbitrary expression as its
252
+ second argument.
253
+
254
+ It shall transform all values which correspond to the context to the value generated by passing
255
+ them to the expression.
256
+
257
+ ##### 2.1.2.13 - sort
258
+
259
+ The sort operator takes a context object as its first argument, and a dynamic list consisting of
260
+ dynamic values where every such value is a boolean as its second argument.
261
+
262
+ It shall sort contents of the context and return it, where every key in the dynamic list specifies
263
+ the key of a field to sort the contents by, with the order of the values determining sorting
264
+ priority. A value of true means ascending order and a value of false means descending order.
package/omniQuery.js ADDED
@@ -0,0 +1,500 @@
1
+ var autoCORS = require("telos-autocors");
2
+ var Database = require('better-sqlite3');
3
+ var { MongoClient } = require("mongodb");
4
+ var pg = require("pg");
5
+ var sqlite3 = require('sqlite3').verbose();
6
+
7
+ // STUB: Selector Format Conversions?
8
+
9
+ /*
10
+
11
+ FORMAT:
12
+
13
+ {
14
+ access: {
15
+ url: "...",
16
+ options: { ... },
17
+ type?: "..." / ["...", ...]
18
+ },
19
+ operation: {
20
+ type: "...",
21
+ data: { ... },
22
+ options: { ... }
23
+ },
24
+ filters: [
25
+ {
26
+ type: "...",
27
+ options: { ... }
28
+ }
29
+ ]
30
+ }
31
+
32
+ */
33
+
34
+ var omniQuery = {
35
+ entangle: (source, target, mutual) => {
36
+ // STUB
37
+ },
38
+ intervals: [],
39
+ middleware: [
40
+ // STUB: Postgres, Mongo, JSON | File / Storage / Memory / Server
41
+ { // MONGO
42
+ match: (data) => {
43
+ return data.access.url.startsWith("mongodb://") ||
44
+ data.access.url.startsWith("mongodb+srv://");
45
+ },
46
+ query: (data, options) => {
47
+
48
+ return new Promise((resolve, reject) => {
49
+
50
+ (async () => {
51
+
52
+ let client = new MongoClient(data.access.url);
53
+ let contents = null;
54
+
55
+ try {
56
+
57
+ let operation =
58
+ data.operation.type.toLowerCase().trim();
59
+
60
+ let types = omniQuery.utils.general.getFilterTypes(
61
+ data.filters
62
+ );
63
+
64
+ await client.connect();
65
+
66
+ let db = client.db(types["at"][0].value);
67
+
68
+ let collection =
69
+ db.collection(types["at"][1].value);
70
+
71
+ if(operation == "read") {
72
+
73
+ contents =
74
+ await collection.find({ }).toArray();
75
+ }
76
+
77
+ if(operation == "create") {
78
+
79
+ contents =
80
+ await collection.insertMany(
81
+ data.operation.data
82
+ );
83
+ }
84
+ }
85
+
86
+ catch(error) {
87
+ console.error(error);
88
+ }
89
+
90
+ await client.close();
91
+
92
+ resolve(contents);
93
+ })();
94
+ });
95
+ }
96
+ },
97
+ { // POSTGRES
98
+ match: (data) => {
99
+ return data.access.url.startsWith("postgres://") ||
100
+ data.access.url.startsWith("postgresql://");
101
+ },
102
+ query: (data, options) => {
103
+
104
+ return new Promise((resolve, reject) => {
105
+
106
+ (async () => {
107
+
108
+ let client = new pg.Client({
109
+ connectionString: data.access.url
110
+ });
111
+
112
+ try {
113
+
114
+ await client.connect();
115
+
116
+ let result = await client.query(
117
+ omniQuery.utils.sql.constructSQL(
118
+ data, { addColumns: true }
119
+ )
120
+ );
121
+
122
+ await client.end();
123
+
124
+ resolve(result.rows);
125
+ }
126
+
127
+ catch(error) {
128
+
129
+ console.error(error);
130
+ client.end();
131
+
132
+ resolve(null);
133
+ }
134
+ })();
135
+ });
136
+ }
137
+ },
138
+ { // SQLITE
139
+ match: (data) => {
140
+ return !data.access.url.includes("://");
141
+ },
142
+ query: (data, options) => {
143
+
144
+ let operation = data.operation.type.toLowerCase().trim();
145
+ let query = omniQuery.utils.sql.constructSQL(data);
146
+
147
+ let db = options.sync ?
148
+ new Database(data.access.url) :
149
+ new sqlite3.Database(data.access.url);
150
+
151
+ try {
152
+
153
+ if(operation == "read") {
154
+
155
+ if(!options.sync) {
156
+
157
+ return new Promise((resolve, reject) => {
158
+
159
+ db.all(query, (error, rows) => {
160
+
161
+ db.close();
162
+
163
+ if(error) {
164
+
165
+ console.error(error);
166
+
167
+ resolve(null);
168
+ }
169
+
170
+ resolve(rows);
171
+ });
172
+ });
173
+ }
174
+
175
+ else {
176
+
177
+ let rows = db.prepare(query).all();
178
+ db.close();
179
+
180
+ return rows;
181
+ }
182
+ }
183
+
184
+ else {
185
+
186
+ if(!options.sync) {
187
+
188
+ return new Promise((resolve, reject) => {
189
+
190
+ db.exec(query, (error) => {
191
+
192
+ db.close();
193
+
194
+ if(error)
195
+ console.error(error);
196
+
197
+ resolve(null);
198
+ });
199
+ });
200
+ }
201
+
202
+ else
203
+ db.exec(query);
204
+ }
205
+ }
206
+
207
+ catch(error) {
208
+ console.error(error);
209
+ }
210
+
211
+ db.close();
212
+
213
+ return null;
214
+ }
215
+ },
216
+ { // STUB: JSON - READ / WRITE -- HTTP
217
+ match: (data) => {
218
+
219
+ },
220
+ query: (data, options) => {
221
+
222
+ }
223
+ }
224
+ ],
225
+ query: (data, options) => {
226
+
227
+ options = options != null ? options : { };
228
+
229
+ try {
230
+
231
+ return (
232
+ options.middleware != null ?
233
+ options.middleware : omniQuery.middleware
234
+ ).filter(item => {
235
+
236
+ try {
237
+ return item.match(data);
238
+ }
239
+
240
+ catch(error) {
241
+ return false;
242
+ }
243
+ })[0].query(data, options);
244
+ }
245
+
246
+ catch(error) {
247
+
248
+ console.log(error.stack);
249
+
250
+ return null;
251
+ }
252
+ },
253
+ subscribe: (target, selector) => {
254
+ // STUB
255
+ },
256
+ utils: {
257
+ general: {
258
+ getFilterTypes: (filters) => {
259
+
260
+ let types = {};
261
+
262
+ filters.forEach(item => {
263
+
264
+ let type = item.type.toLowerCase().trim();
265
+
266
+ types[type] = types[type] != null ? types[type] : [];
267
+ types[type].push(item.options);
268
+ });
269
+
270
+ return types
271
+ }
272
+ },
273
+ sql: {
274
+ constructSQL: (data, options) => { // STUB: PREVENT INJECTION
275
+
276
+ let types = omniQuery.utils.general.getFilterTypes(
277
+ data.filters
278
+ );
279
+
280
+ if(data.operation.type.toLowerCase().trim() == "read") {
281
+
282
+ return `SELECT ${
283
+ types["focus"] == null ?
284
+ "*" : types["focus"][0].value.join(",")
285
+ } FROM ${types["at"][0].value}${
286
+ types["filter"] != null ?
287
+ ` WHERE ${
288
+ types["filter"].map(
289
+ item =>
290
+ omniQuery.utils.sql.constructSQLFilter(
291
+ item.value
292
+ )
293
+ ).join(" AND ")
294
+ }` :
295
+ ""
296
+ }${
297
+ types["crop"] != null ?
298
+ ` LIMIT ${types["crop"][0].value}` :
299
+ ""
300
+ };`;
301
+ }
302
+
303
+ if(data.operation.type.toLowerCase().trim() == "create" ||
304
+ (
305
+ data.operation.type.toLowerCase().trim() == "update" &&
306
+ types["filter"] == null
307
+ )
308
+ ) {
309
+
310
+ let columns = { };
311
+
312
+ data.operation.data =
313
+ Array.isArray(data.operation.data[0]) ?
314
+ data.operation.data[0] : data.operation.data;
315
+
316
+ data.operation.data.forEach(item => {
317
+ columns = Object.assign(columns, item);
318
+ });
319
+
320
+ return `${
321
+ data.operation.type.toLowerCase().trim() == "update" ?
322
+ `DROP TABLE IF EXISTS ${types["at"][0].value}; ` :
323
+ ""
324
+ }CREATE TABLE IF NOT EXISTS ${
325
+ types["at"][0].value
326
+ } (${
327
+ Object.keys(columns).map(column =>
328
+ `${
329
+ column
330
+ } ${
331
+ {
332
+ "string": "TEXT",
333
+ "number": "DECIMAL",
334
+ "boolean": "BOOLEAN"
335
+ }[typeof columns[column]]
336
+ }`
337
+ ).join(",")
338
+ }); ${options.addColumns ?
339
+ Object.keys(columns).map(column =>
340
+ `ALTER TABLE ${
341
+ types["at"][0].value
342
+ } ADD COLUMN IF NOT EXISTS ${
343
+ column
344
+ } ${
345
+ {
346
+ "string": "TEXT",
347
+ "number": "DECIMAL",
348
+ "boolean": "BOOLEAN"
349
+ }[typeof columns[column]]
350
+ };`
351
+ ).join(" ") :
352
+ ""
353
+ } INSERT INTO ${
354
+ types["at"][0].value
355
+ } (${
356
+ Object.keys(columns).join(",")
357
+ }) VALUES ${
358
+ data.operation.data.map(item => {
359
+ return `(${Object.keys(columns).map(
360
+ (column) => {
361
+
362
+ if(typeof item[column] == "string")
363
+ return `'${item[column]}'`;
364
+
365
+ if(typeof item[column] == "boolean")
366
+ return `${item[column]}`.toUpperCase();
367
+
368
+ if(typeof item[column] == "number") {
369
+
370
+ return `${
371
+ item[column]
372
+ }${
373
+ item[column] % 1 == 0 ? ".0" : ""
374
+ }`;
375
+ }
376
+
377
+ return `${item[column]}`;
378
+ }
379
+ ).join(",")})`
380
+ }).join(",")
381
+ };`;
382
+ }
383
+
384
+ if(
385
+ data.operation.type.toLowerCase().trim() == "update" &&
386
+ types["filter"] != null
387
+ ) {
388
+
389
+ let columns = data.operation.data[0];
390
+
391
+ return `${options.addColumns ?
392
+ Object.keys(columns).map(column =>
393
+ `ALTER TABLE ${
394
+ types["at"][0].value
395
+ } ADD COLUMN IF NOT EXISTS ${
396
+ column
397
+ } ${
398
+ {
399
+ "string": "TEXT",
400
+ "number": "DECIMAL",
401
+ "boolean": "BOOLEAN"
402
+ }[typeof columns[column]]
403
+ };`
404
+ ).join(" ") :
405
+ ""
406
+ } UPDATE ${
407
+ types["at"][0].value
408
+ } SET ${
409
+ Object.keys(columns).map(
410
+ (column) => {
411
+
412
+ if(typeof columns[column] == "string")
413
+ return `${column} = '${columns[column]}'`;
414
+
415
+ if(typeof columns[column] == "boolean") {
416
+
417
+ return `${
418
+ column
419
+ } = ${
420
+ ("" + columns[column]).toUpperCase()
421
+ }`;
422
+ }
423
+
424
+ if(typeof columns[column] == "number") {
425
+
426
+ return `${column} = ${
427
+ columns[column]
428
+ }${
429
+ columns[column] % 1 == 0 ? ".0" : ""
430
+ }`;
431
+ }
432
+
433
+ return `${column} = ${columns[column]}`;
434
+ }
435
+ ).join(",")
436
+ } WHERE ${
437
+ types["filter"].map(
438
+ item =>
439
+ omniQuery.utils.sql.constructSQLFilter(
440
+ item.value
441
+ )
442
+ ).join(" AND ")
443
+ };`;
444
+ }
445
+
446
+ if(data.operation.type.toLowerCase().trim() == "delete") {
447
+
448
+ return types["filter"] != null ?
449
+ `DELETE FROM ${types["at"][0].value} WHERE ${
450
+ types["filter"].map(
451
+ item =>
452
+ omniQuery.utils.sql.constructSQLFilter(
453
+ item.value
454
+ )
455
+ ).join(" AND ")
456
+ }` :
457
+ `DROP TABLE ${types["at"][0].value};`;
458
+ }
459
+ },
460
+ constructSQLFilter: (filter) => {
461
+
462
+ filter = filter.map(
463
+ item => Array.isArray(item) ?
464
+ omniQuery.utils.sql.constructSQLFilter(item) : item
465
+ );
466
+
467
+ switch(filter[0].toLowerCase().trim()) {
468
+
469
+ case "and": return filter.slice(1).join(" AND ");
470
+ case "or": return filter.slice(1).join(" OR ");
471
+
472
+ case "equals": return filter.map((item, index) => {
473
+ return `${item} = ${filter[index + 1]}`;
474
+ }).slice(1, filter.length - 1).join(" AND ");
475
+
476
+ case "less": return filter.map((item, index) => {
477
+ return `${item} < ${filter[index + 1]}`;
478
+ }).slice(1, filter.length - 1).join(" AND ");
479
+
480
+ case "greater": return filter.map((item, index) => {
481
+ return `${item} > ${filter[index + 1]}`;
482
+ }).slice(1, filter.length - 1).join(" AND ");
483
+
484
+ case "gte": return filter.map((item, index) => {
485
+ return `${item} >= ${filter[index + 1]}`;
486
+ }).slice(1, filter.length - 1).join(" AND ");
487
+
488
+ case "lte": return filter.map((item, index) => {
489
+ return `${item} <= ${filter[index + 1]}`;
490
+ }).slice(1, filter.length - 1).join(" AND ");
491
+
492
+ default: return "";
493
+ }
494
+ },
495
+ }
496
+ }
497
+ };
498
+
499
+ if(typeof module == "object")
500
+ module.exports = omniQuery;
package/oql.js ADDED
@@ -0,0 +1,182 @@
1
+ let utils = {
2
+ normalizeContext: (context) => {
3
+
4
+ context.access = context.access != null ? context.access : {};
5
+ context.operation = context.operation != null ? context.operation : {};
6
+ context.filters = context.filters != null ? context.filters : [];
7
+
8
+ return context;
9
+ },
10
+ normalizeValue: (value) => {
11
+
12
+ value = JSON.parse("" + value);
13
+
14
+ if(typeof value == "string") {
15
+
16
+ if(value.startsWith("\"") && value.endsWith("\""))
17
+ value = value.substring(1, value.length - 1);
18
+ }
19
+
20
+ return value;
21
+ }
22
+ };
23
+
24
+ module.exports = [
25
+ {
26
+ process: (context, args) => {
27
+
28
+ if(context.local.operator != "access")
29
+ return null;
30
+
31
+ return JSON.stringify(utils.normalizeContext({
32
+ access: {
33
+ url: utils.normalizeValue(args[0]),
34
+ options: args[1] != null ?
35
+ JSON.parse("" + args[1]) :
36
+ null
37
+ },
38
+ operation: {
39
+ type: "read"
40
+ }
41
+ }));
42
+ },
43
+ tags: ["oql", "access"]
44
+ },
45
+ {
46
+ process: (context, args) => {
47
+
48
+ if(context.local.operator != "append")
49
+ return null;
50
+
51
+ if(!args[0].startsWith("{"))
52
+ return null; // STUB
53
+
54
+ let data = utils.normalizeContext(JSON.parse(args[0]));
55
+
56
+ data.operation.type = "create"
57
+ data.operation.data = args.slice(1).map(item => JSON.parse(item));
58
+
59
+ return JSON.stringify(data);
60
+ },
61
+ tags: ["oql", "append"]
62
+ },
63
+ {
64
+ process: (context, args) => {
65
+
66
+ if(context.local.operator != "at")
67
+ return null;
68
+
69
+ if(!args[0].startsWith("{"))
70
+ return `${args[0]}[${args[1]}]`;
71
+
72
+ let data = utils.normalizeContext(JSON.parse(args[0]));
73
+
74
+ args.slice(1).forEach(item => {
75
+
76
+ data.filters.push({
77
+ type: "at",
78
+ options: {
79
+ value: utils.normalizeValue(item)
80
+ }
81
+ });
82
+ });
83
+
84
+ return JSON.stringify(data);
85
+ },
86
+ tags: ["oql", "at"]
87
+ },
88
+ {
89
+ process: (context, args) => {
90
+
91
+ if(context.local.operator != "filter")
92
+ return null;
93
+
94
+ let data = utils.normalizeContext(JSON.parse(args[0]));
95
+
96
+ data.filters.push({
97
+ type: "filter",
98
+ options: {
99
+ value: context.local.list[2]
100
+ }
101
+ });
102
+
103
+ return JSON.stringify(data);
104
+ },
105
+ tags: ["oql", "filter"]
106
+ },
107
+ {
108
+ process: (context, args) => {
109
+
110
+ if(context.local.operator != "remove")
111
+ return null;
112
+
113
+ if(!args[0].startsWith("{"))
114
+ return null; // STUB
115
+
116
+ let data = utils.normalizeContext(JSON.parse(args[0]));
117
+ data.operation.type = "delete"
118
+
119
+ return JSON.stringify(data);
120
+ },
121
+ tags: ["oql", "remove"]
122
+ },
123
+ {
124
+ process: (context, args) => {
125
+
126
+ if(context.local.operator != "set")
127
+ return null;
128
+
129
+ if(!args[0].startsWith("{")) {
130
+
131
+ return context.state[args[0]] != null ?
132
+ `${args[0]}=context.state[${args[0]}];` :
133
+ `${
134
+ args[0]
135
+ }=${
136
+ args[1]
137
+ },context.state["${
138
+ args[0]
139
+ }"]=${
140
+ args[0]
141
+ };`
142
+ }
143
+
144
+ let data = utils.normalizeContext(JSON.parse(args[0]));
145
+
146
+ data.operation.type = "update"
147
+ data.operation.data = args.slice(1).map(item => JSON.parse(item));
148
+
149
+ return JSON.stringify(data);
150
+ },
151
+ tags: ["oql", "set"]
152
+ },
153
+ {
154
+ process: (context, args) => {
155
+
156
+ if(context.local.operator != "sort")
157
+ return null;
158
+
159
+ let data = utils.normalizeContext(JSON.parse(args[0]));
160
+
161
+ data.filters.push({
162
+ type: "sort",
163
+ options: {
164
+ value: JSON.parse(args[1])
165
+ }
166
+ });
167
+
168
+ return JSON.stringify(data);
169
+ },
170
+ tags: ["oql", "sort"]
171
+ },
172
+ {
173
+ process: (context, args) => {
174
+
175
+ if(context.local.operator != "query")
176
+ return null;
177
+
178
+ return `(require("telos-oql/omniQuery.js").query(${args[0]}))\n`;
179
+ },
180
+ tags: ["oql", "query"]
181
+ }
182
+ ];
package/oql.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "utilities": {
3
+ "standard": {
4
+ "source": "telos-oql/oql.js",
5
+ "properties": {
6
+ "type": "fusion-lisp"
7
+ }
8
+ }
9
+ }
10
+ }
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "telos-oql",
3
+ "version": "1.0.0",
4
+ "description": "A LISP dialect for database-agnostic querying",
5
+ "main": "oql.json",
6
+ "dependencies": {
7
+ "fusion-lisp": "~1.0.0",
8
+ "mongodb": "~1.0.0",
9
+ "pg": "~1.0.0",
10
+ "sqlite3": "~1.0.0",
11
+ "telos-autocors": "~1.0.0"
12
+ },
13
+ "author": "",
14
+ "license": "Apache-2.0",
15
+ "homepage": "https://github.com/Telos-Project/OmniQuery"
16
+ }