@subsquid/openreader 1.0.1 → 2.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.
Files changed (279) hide show
  1. package/bin/main.js +2 -1
  2. package/lib/context.d.ts +11 -0
  3. package/lib/context.d.ts.map +1 -0
  4. package/lib/context.js +3 -0
  5. package/lib/context.js.map +1 -0
  6. package/lib/db.d.ts +23 -0
  7. package/lib/db.d.ts.map +1 -0
  8. package/lib/db.js +57 -0
  9. package/lib/db.js.map +1 -0
  10. package/{dist → lib}/dialect.d.ts +0 -0
  11. package/{dist → lib}/dialect.d.ts.map +0 -0
  12. package/{dist → lib}/dialect.js +0 -0
  13. package/{dist → lib}/dialect.js.map +0 -0
  14. package/lib/ir/args.d.ts +47 -0
  15. package/lib/ir/args.d.ts.map +1 -0
  16. package/lib/ir/args.js +3 -0
  17. package/lib/ir/args.js.map +1 -0
  18. package/lib/ir/connection.d.ts +30 -0
  19. package/lib/ir/connection.d.ts.map +1 -0
  20. package/lib/ir/connection.js +17 -0
  21. package/lib/ir/connection.js.map +1 -0
  22. package/lib/ir/fields.d.ts +22 -0
  23. package/lib/ir/fields.d.ts.map +1 -0
  24. package/lib/ir/fields.js +3 -0
  25. package/lib/ir/fields.js.map +1 -0
  26. package/lib/limit.size.d.ts +3 -0
  27. package/lib/limit.size.d.ts.map +1 -0
  28. package/lib/limit.size.js +44 -0
  29. package/lib/limit.size.js.map +1 -0
  30. package/{dist → lib}/main.d.ts +0 -0
  31. package/{dist → lib}/main.d.ts.map +0 -0
  32. package/lib/main.js +53 -0
  33. package/lib/main.js.map +1 -0
  34. package/{dist → lib}/model.d.ts +10 -1
  35. package/lib/model.d.ts.map +1 -0
  36. package/{dist → lib}/model.js +0 -0
  37. package/{dist → lib}/model.js.map +0 -0
  38. package/{dist/gql/schema.d.ts → lib/model.schema.d.ts} +2 -2
  39. package/lib/model.schema.d.ts.map +1 -0
  40. package/{dist/gql/schema.js → lib/model.schema.js} +44 -7
  41. package/lib/model.schema.js.map +1 -0
  42. package/{dist → lib}/model.tools.d.ts +0 -0
  43. package/{dist → lib}/model.tools.d.ts.map +0 -0
  44. package/{dist → lib}/model.tools.js +0 -0
  45. package/{dist → lib}/model.tools.js.map +0 -0
  46. package/{dist → lib/opencrud}/orderBy.d.ts +2 -5
  47. package/lib/opencrud/orderBy.d.ts.map +1 -0
  48. package/{dist → lib/opencrud}/orderBy.js +1 -1
  49. package/lib/opencrud/orderBy.js.map +1 -0
  50. package/lib/opencrud/schema.d.ts +31 -0
  51. package/lib/opencrud/schema.d.ts.map +1 -0
  52. package/lib/opencrud/schema.js +522 -0
  53. package/lib/opencrud/schema.js.map +1 -0
  54. package/lib/opencrud/tree.d.ts +8 -0
  55. package/lib/opencrud/tree.d.ts.map +1 -0
  56. package/lib/opencrud/tree.js +131 -0
  57. package/lib/opencrud/tree.js.map +1 -0
  58. package/lib/opencrud/where.d.ts +7 -0
  59. package/lib/opencrud/where.d.ts.map +1 -0
  60. package/lib/opencrud/where.js +141 -0
  61. package/lib/opencrud/where.js.map +1 -0
  62. package/{dist/gql → lib}/scalars/BigInt.d.ts +0 -0
  63. package/lib/scalars/BigInt.d.ts.map +1 -0
  64. package/{dist/gql → lib}/scalars/BigInt.js +1 -1
  65. package/lib/scalars/BigInt.js.map +1 -0
  66. package/{dist/gql → lib}/scalars/Bytes.d.ts +0 -0
  67. package/lib/scalars/Bytes.d.ts.map +1 -0
  68. package/{dist/gql → lib}/scalars/Bytes.js +2 -2
  69. package/lib/scalars/Bytes.js.map +1 -0
  70. package/{dist/gql → lib}/scalars/DateTime.d.ts +0 -0
  71. package/lib/scalars/DateTime.d.ts.map +1 -0
  72. package/{dist/gql → lib}/scalars/DateTime.js +1 -1
  73. package/lib/scalars/DateTime.js.map +1 -0
  74. package/{dist/gql → lib}/scalars/JSON.d.ts +0 -0
  75. package/lib/scalars/JSON.d.ts.map +1 -0
  76. package/{dist/gql → lib}/scalars/JSON.js +0 -0
  77. package/lib/scalars/JSON.js.map +1 -0
  78. package/{dist/gql → lib}/scalars/index.d.ts +0 -0
  79. package/lib/scalars/index.d.ts.map +1 -0
  80. package/{dist/gql → lib}/scalars/index.js +0 -0
  81. package/lib/scalars/index.js.map +1 -0
  82. package/lib/server.d.ts +38 -0
  83. package/lib/server.d.ts.map +1 -0
  84. package/lib/server.js +146 -0
  85. package/lib/server.js.map +1 -0
  86. package/lib/sql/cursor.d.ts +52 -0
  87. package/lib/sql/cursor.d.ts.map +1 -0
  88. package/lib/sql/cursor.js +234 -0
  89. package/lib/sql/cursor.js.map +1 -0
  90. package/lib/sql/mapping.d.ts +4 -0
  91. package/lib/sql/mapping.d.ts.map +1 -0
  92. package/lib/sql/mapping.js +71 -0
  93. package/lib/sql/mapping.js.map +1 -0
  94. package/lib/sql/printer.d.ts +37 -0
  95. package/lib/sql/printer.d.ts.map +1 -0
  96. package/lib/sql/printer.js +311 -0
  97. package/lib/sql/printer.js.map +1 -0
  98. package/lib/sql/query.d.ts +46 -0
  99. package/lib/sql/query.d.ts.map +1 -0
  100. package/lib/sql/query.js +134 -0
  101. package/lib/sql/query.js.map +1 -0
  102. package/lib/sql/util.d.ts +30 -0
  103. package/lib/sql/util.d.ts.map +1 -0
  104. package/lib/sql/util.js +75 -0
  105. package/lib/sql/util.js.map +1 -0
  106. package/lib/subscription.d.ts +18 -0
  107. package/lib/subscription.d.ts.map +1 -0
  108. package/lib/subscription.js +47 -0
  109. package/lib/subscription.js.map +1 -0
  110. package/{dist → lib}/test/basic.test.d.ts +0 -0
  111. package/{dist → lib}/test/basic.test.d.ts.map +0 -0
  112. package/{dist → lib}/test/basic.test.js +0 -0
  113. package/{dist → lib}/test/basic.test.js.map +0 -0
  114. package/{dist → lib}/test/connection.test.d.ts +0 -0
  115. package/{dist → lib}/test/connection.test.d.ts.map +0 -0
  116. package/{dist → lib}/test/connection.test.js +0 -0
  117. package/{dist → lib}/test/connection.test.js.map +0 -0
  118. package/{dist → lib}/test/fts.test.d.ts +0 -0
  119. package/{dist → lib}/test/fts.test.d.ts.map +0 -0
  120. package/{dist → lib}/test/fts.test.js +1 -1
  121. package/lib/test/fts.test.js.map +1 -0
  122. package/{dist → lib}/test/isNull.test.d.ts +0 -0
  123. package/{dist → lib}/test/isNull.test.d.ts.map +0 -0
  124. package/{dist → lib}/test/isNull.test.js +0 -0
  125. package/{dist → lib}/test/isNull.test.js.map +0 -0
  126. package/{dist → lib}/test/lists.test.d.ts +0 -0
  127. package/{dist → lib}/test/lists.test.d.ts.map +0 -0
  128. package/{dist → lib}/test/lists.test.js +0 -0
  129. package/{dist → lib}/test/lists.test.js.map +0 -0
  130. package/{dist → lib}/test/lookup.test.d.ts +0 -0
  131. package/{dist → lib}/test/lookup.test.d.ts.map +0 -0
  132. package/{dist → lib}/test/lookup.test.js +0 -0
  133. package/{dist → lib}/test/lookup.test.js.map +0 -0
  134. package/{dist → lib}/test/regressions.test.d.ts +0 -0
  135. package/{dist → lib}/test/regressions.test.d.ts.map +0 -0
  136. package/{dist → lib}/test/regressions.test.js +0 -0
  137. package/{dist → lib}/test/regressions.test.js.map +0 -0
  138. package/{dist → lib}/test/scalars.test.d.ts +0 -0
  139. package/{dist → lib}/test/scalars.test.d.ts.map +0 -0
  140. package/{dist → lib}/test/scalars.test.js +0 -0
  141. package/{dist → lib}/test/scalars.test.js.map +0 -0
  142. package/{dist → lib}/test/setup.d.ts +3 -1
  143. package/lib/test/setup.d.ts.map +1 -0
  144. package/{dist → lib}/test/setup.js +14 -12
  145. package/lib/test/setup.js.map +1 -0
  146. package/lib/test/subscription.test.d.ts +2 -0
  147. package/lib/test/subscription.test.d.ts.map +1 -0
  148. package/lib/test/subscription.test.js +99 -0
  149. package/lib/test/subscription.test.js.map +1 -0
  150. package/{dist → lib}/test/tools.test.d.ts +0 -0
  151. package/{dist → lib}/test/tools.test.d.ts.map +0 -0
  152. package/{dist → lib}/test/tools.test.js +0 -0
  153. package/{dist → lib}/test/tools.test.js.map +0 -0
  154. package/{dist → lib}/test/typed-json.test.d.ts +0 -0
  155. package/{dist → lib}/test/typed-json.test.d.ts.map +0 -0
  156. package/{dist → lib}/test/typed-json.test.js +0 -0
  157. package/{dist → lib}/test/typed-json.test.js.map +0 -0
  158. package/{dist → lib}/test/unions.test.d.ts +0 -0
  159. package/{dist → lib}/test/unions.test.d.ts.map +0 -0
  160. package/{dist → lib}/test/unions.test.js +0 -0
  161. package/{dist → lib}/test/unions.test.js.map +0 -0
  162. package/{dist → lib}/test/where.test.d.ts +0 -0
  163. package/{dist → lib}/test/where.test.d.ts.map +0 -0
  164. package/{dist → lib}/test/where.test.js +0 -0
  165. package/{dist → lib}/test/where.test.js.map +0 -0
  166. package/{dist → lib}/tools.d.ts +0 -0
  167. package/{dist → lib}/tools.d.ts.map +0 -0
  168. package/{dist → lib}/tools.js +3 -3
  169. package/{dist → lib}/tools.js.map +1 -1
  170. package/lib/util/error-handling.d.ts +11 -0
  171. package/lib/util/error-handling.d.ts.map +1 -0
  172. package/lib/util/error-handling.js +42 -0
  173. package/lib/util/error-handling.js.map +1 -0
  174. package/lib/util/lazy-transaction.d.ts +10 -0
  175. package/lib/util/lazy-transaction.d.ts.map +1 -0
  176. package/lib/util/lazy-transaction.js +43 -0
  177. package/lib/util/lazy-transaction.js.map +1 -0
  178. package/lib/util/resolve-tree.d.ts +14 -0
  179. package/lib/util/resolve-tree.d.ts.map +1 -0
  180. package/lib/util/resolve-tree.js +52 -0
  181. package/lib/util/resolve-tree.js.map +1 -0
  182. package/{dist → lib/util}/util.d.ts +2 -3
  183. package/lib/util/util.d.ts.map +1 -0
  184. package/{dist → lib/util}/util.js +9 -13
  185. package/lib/util/util.js.map +1 -0
  186. package/package.json +17 -9
  187. package/src/context.ts +14 -0
  188. package/src/db.ts +46 -57
  189. package/src/ir/args.ts +85 -0
  190. package/src/ir/connection.ts +48 -0
  191. package/src/ir/fields.ts +40 -0
  192. package/src/limit.size.ts +46 -0
  193. package/src/main.ts +62 -37
  194. package/src/{gql/schema.ts → model.schema.ts} +51 -8
  195. package/src/model.ts +12 -1
  196. package/src/{orderBy.ts → opencrud/orderBy.ts} +3 -10
  197. package/src/opencrud/schema.ts +632 -0
  198. package/src/opencrud/tree.ts +144 -0
  199. package/src/opencrud/where.ts +141 -0
  200. package/src/{gql/scalars → scalars}/BigInt.ts +1 -1
  201. package/src/{gql/scalars → scalars}/Bytes.ts +4 -4
  202. package/src/{gql/scalars → scalars}/DateTime.ts +1 -1
  203. package/src/{gql/scalars → scalars}/JSON.ts +0 -0
  204. package/src/{gql/scalars → scalars}/index.ts +0 -0
  205. package/src/server.ts +128 -48
  206. package/src/sql/cursor.ts +291 -0
  207. package/src/sql/mapping.ts +66 -0
  208. package/src/sql/printer.ts +328 -0
  209. package/src/sql/query.ts +194 -0
  210. package/src/sql/util.ts +89 -0
  211. package/src/subscription.ts +46 -0
  212. package/src/test/fts.test.ts +1 -1
  213. package/src/test/setup.ts +12 -10
  214. package/src/test/subscription.test.ts +98 -0
  215. package/src/tools.ts +1 -1
  216. package/src/util/error-handling.ts +40 -0
  217. package/src/util/lazy-transaction.ts +49 -0
  218. package/src/util/resolve-tree.ts +65 -0
  219. package/src/{util.ts → util/util.ts} +9 -14
  220. package/dist/db.d.ts +0 -28
  221. package/dist/db.d.ts.map +0 -1
  222. package/dist/db.js +0 -69
  223. package/dist/db.js.map +0 -1
  224. package/dist/gql/opencrud.d.ts +0 -6
  225. package/dist/gql/opencrud.d.ts.map +0 -1
  226. package/dist/gql/opencrud.js +0 -326
  227. package/dist/gql/opencrud.js.map +0 -1
  228. package/dist/gql/scalars/BigInt.d.ts.map +0 -1
  229. package/dist/gql/scalars/BigInt.js.map +0 -1
  230. package/dist/gql/scalars/Bytes.d.ts.map +0 -1
  231. package/dist/gql/scalars/Bytes.js.map +0 -1
  232. package/dist/gql/scalars/DateTime.d.ts.map +0 -1
  233. package/dist/gql/scalars/DateTime.js.map +0 -1
  234. package/dist/gql/scalars/JSON.d.ts.map +0 -1
  235. package/dist/gql/scalars/JSON.js.map +0 -1
  236. package/dist/gql/scalars/index.d.ts.map +0 -1
  237. package/dist/gql/scalars/index.js.map +0 -1
  238. package/dist/gql/schema.d.ts.map +0 -1
  239. package/dist/gql/schema.js.map +0 -1
  240. package/dist/main.js +0 -43
  241. package/dist/main.js.map +0 -1
  242. package/dist/model.d.ts.map +0 -1
  243. package/dist/orderBy.d.ts.map +0 -1
  244. package/dist/orderBy.js.map +0 -1
  245. package/dist/queryBuilder.d.ts +0 -56
  246. package/dist/queryBuilder.d.ts.map +0 -1
  247. package/dist/queryBuilder.js +0 -733
  248. package/dist/queryBuilder.js.map +0 -1
  249. package/dist/relayConnection.d.ts +0 -37
  250. package/dist/relayConnection.d.ts.map +0 -1
  251. package/dist/relayConnection.js +0 -43
  252. package/dist/relayConnection.js.map +0 -1
  253. package/dist/requestedFields.d.ts +0 -33
  254. package/dist/requestedFields.d.ts.map +0 -1
  255. package/dist/requestedFields.js +0 -179
  256. package/dist/requestedFields.js.map +0 -1
  257. package/dist/resolver.d.ts +0 -9
  258. package/dist/resolver.d.ts.map +0 -1
  259. package/dist/resolver.js +0 -158
  260. package/dist/resolver.js.map +0 -1
  261. package/dist/server.d.ts +0 -22
  262. package/dist/server.d.ts.map +0 -1
  263. package/dist/server.js +0 -96
  264. package/dist/server.js.map +0 -1
  265. package/dist/test/fts.test.js.map +0 -1
  266. package/dist/test/setup.d.ts.map +0 -1
  267. package/dist/test/setup.js.map +0 -1
  268. package/dist/util.d.ts.map +0 -1
  269. package/dist/util.js.map +0 -1
  270. package/dist/where.d.ts +0 -9
  271. package/dist/where.d.ts.map +0 -1
  272. package/dist/where.js +0 -101
  273. package/dist/where.js.map +0 -1
  274. package/src/gql/opencrud.ts +0 -350
  275. package/src/queryBuilder.ts +0 -891
  276. package/src/relayConnection.ts +0 -80
  277. package/src/requestedFields.ts +0 -246
  278. package/src/resolver.ts +0 -201
  279. package/src/where.ts +0 -119
@@ -0,0 +1,291 @@
1
+ import {assertNotNull, unexpectedCase} from "@subsquid/util-internal"
2
+ import {toSnakeCase} from "@subsquid/util-naming"
3
+ import assert from "assert"
4
+ import {Dialect} from "../dialect"
5
+ import {Entity, JsonObject, Model, ObjectPropType, Prop, UnionPropType} from "../model"
6
+ import {getEntity, getFtsQuery, getObject, getUnionProps} from "../model.tools"
7
+ import {toColumn, toFkColumn, toTable} from "../util/util"
8
+ import {AliasSet, escapeIdentifier, JoinSet} from "./util"
9
+
10
+
11
+ export interface CursorCtx {
12
+ model: Model
13
+ dialect: Dialect
14
+ aliases: AliasSet
15
+ join: JoinSet
16
+ }
17
+
18
+
19
+ export interface Cursor {
20
+ output(field: string): string
21
+ native(field: string): string
22
+ ref(field: string): string
23
+ child(field: string): Cursor
24
+ prop(field: string): Prop
25
+ }
26
+
27
+
28
+ export class EntityCursor implements Cursor {
29
+ public readonly entity: Entity
30
+ public readonly table: string
31
+ public readonly tableAlias: string
32
+
33
+ constructor(
34
+ private ctx: CursorCtx,
35
+ private entityName: string,
36
+ joined?: {on: string, rhs: string}
37
+ ) {
38
+ this.entity = getEntity(this.ctx.model, this.entityName)
39
+ this.table = toTable(this.entityName)
40
+ if (joined) {
41
+ this.tableAlias = this.ctx.join.add(this.table, this._columnName(joined.on), joined.rhs)
42
+ } else {
43
+ this.tableAlias = this.ctx.aliases.add(this.table)
44
+ }
45
+ }
46
+
47
+ private ident(name: string): string {
48
+ return escapeIdentifier(this.ctx.dialect, name)
49
+ }
50
+
51
+ private column(field: string): string {
52
+ return this.ident(this.tableAlias) + "." + this.ident(this._columnName(field))
53
+ }
54
+
55
+ private _columnName(field: string): string {
56
+ let prop = this.prop(field)
57
+ if (prop.type.kind == 'fk') {
58
+ return toFkColumn(field)
59
+ } else {
60
+ return toColumn(field)
61
+ }
62
+ }
63
+
64
+ prop(field: string): Prop {
65
+ return assertNotNull(this.entity.properties[field])
66
+ }
67
+
68
+ output(field: string): string {
69
+ let col = this.column(field)
70
+ let prop = this.prop(field)
71
+ switch(prop.type.kind) {
72
+ case "scalar":
73
+ switch(prop.type.name) {
74
+ case "BigInt":
75
+ return `(${col})::text`
76
+ case "Bytes":
77
+ return `'0x' || encode(${col}, 'hex')`
78
+ case "DateTime":
79
+ if (this.ctx.dialect == "cockroach") {
80
+ return `experimental_strftime((${col}) at time zone 'UTC', '%Y-%m-%dT%H:%M:%S.%fZ')`
81
+ } else {
82
+ return `to_char((${col}) at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`
83
+ }
84
+ default:
85
+ return col
86
+ }
87
+ case "enum":
88
+ return col
89
+ case "list": {
90
+ let itemType = prop.type.item.type
91
+ switch(itemType.kind) {
92
+ case "scalar":
93
+ case "enum":
94
+ switch(itemType.name) {
95
+ case "BigInt":
96
+ return `(${col})::text[]`
97
+ case "Bytes":
98
+ return `array(select '0x' || encode(i, 'hex') from unnest(${col}) as i)`
99
+ case "DateTime":
100
+ if (this.ctx.dialect == "cockroach") {
101
+ return `array(select experimental_strftime(i at time zone 'UTC', '%Y-%m-%dT%H:%M:%S.%fZ') from unnest(${col}) as i)`
102
+ } else {
103
+ return `array(select to_char(i at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"') from unnest(${col}) as i)`
104
+ }
105
+ default:
106
+ return col
107
+ }
108
+ default:
109
+ return col
110
+ }
111
+ }
112
+ default:
113
+ throw unexpectedCase(prop.type.kind)
114
+ }
115
+ }
116
+
117
+ native(field: string): string {
118
+ let prop = this.prop(field)
119
+ switch(prop.type.kind) {
120
+ case "fk":
121
+ case "scalar":
122
+ case "enum":
123
+ return this.column(field)
124
+ default:
125
+ throw unexpectedCase(prop.type.kind)
126
+ }
127
+ }
128
+
129
+ ref(field: string): string {
130
+ let prop = this.prop(field)
131
+ switch(prop.type.kind) {
132
+ case "fk":
133
+ case "scalar":
134
+ case "enum":
135
+ case "union":
136
+ case "object":
137
+ case "list":
138
+ return this.column(field)
139
+ default:
140
+ throw unexpectedCase(prop.type.kind)
141
+ }
142
+ }
143
+
144
+ child(field: string): Cursor {
145
+ let prop = this.entity.properties[field]
146
+ switch(prop.type.kind) {
147
+ case "object":
148
+ case "union":
149
+ return new ObjectCursor(
150
+ this.ctx,
151
+ this.column(field),
152
+ prop.type
153
+ )
154
+ case "fk":
155
+ return new EntityCursor(
156
+ this.ctx,
157
+ prop.type.foreignEntity,
158
+ {on: 'id', rhs: this.native(field)}
159
+ )
160
+ case "lookup":
161
+ return new EntityCursor(
162
+ this.ctx,
163
+ prop.type.entity,
164
+ {on: prop.type.field, rhs: this.column('id')}
165
+ )
166
+ default:
167
+ throw unexpectedCase(prop.type.kind)
168
+ }
169
+ }
170
+
171
+ tsv(queryName: string): string {
172
+ return this.ident(this.tableAlias) + "." + this.ident(toSnakeCase(queryName) + "_tsv")
173
+ }
174
+
175
+ doc(queryName: string): string {
176
+ let query = getFtsQuery(this.ctx.model, queryName)
177
+ let src = query.sources.find(src => src.entity == this.entityName)
178
+ assert(src != null)
179
+ return src.fields.map(f => `coalesce(${this.column(f)}, '')`).join(` || E'\\n\\n' || `)
180
+ }
181
+ }
182
+
183
+
184
+ export class ObjectCursor implements Cursor {
185
+ private object: JsonObject
186
+ public readonly isUnion: boolean
187
+
188
+ constructor(
189
+ private ctx: CursorCtx,
190
+ private prefix: string,
191
+ type: ObjectPropType | UnionPropType
192
+ ) {
193
+ if (type.kind == 'union') {
194
+ this.isUnion = true
195
+ this.object = getUnionProps(this.ctx.model, type.name)
196
+ } else {
197
+ this.isUnion = false
198
+ this.object = getObject(this.ctx.model, type.name)
199
+ }
200
+ }
201
+
202
+ private json(field: string): string {
203
+ return `${this.prefix}->'${field}'`
204
+ }
205
+
206
+ private string(field: string): string {
207
+ return `${this.prefix}->>'${field}'`
208
+ }
209
+
210
+ prop(field: string): Prop {
211
+ return assertNotNull(this.object.properties[field])
212
+ }
213
+
214
+ output(field: string): string {
215
+ let prop = this.prop(field)
216
+ switch(prop.type.kind) {
217
+ case "scalar":
218
+ switch(prop.type.name) {
219
+ case 'Int':
220
+ return `(${this.json(field)})::integer`
221
+ case 'Float':
222
+ return `(${this.json(field)})::numeric`
223
+ case 'Boolean':
224
+ return `(${this.string(field)})::bool`
225
+ case 'JSON':
226
+ return this.json(field)
227
+ default:
228
+ return this.string(field)
229
+ }
230
+ case "enum":
231
+ return this.string(field)
232
+ case "list":
233
+ return this.json(field)
234
+ default:
235
+ throw unexpectedCase(prop.type.kind)
236
+ }
237
+ }
238
+
239
+ native(field: string): string {
240
+ let prop = this.prop(field)
241
+ switch(prop.type.kind) {
242
+ case "fk":
243
+ case "enum":
244
+ return this.string(field)
245
+ case "scalar":
246
+ switch(prop.type.name) {
247
+ case 'Int':
248
+ return `(${this.json(field)})::integer`
249
+ case 'Float':
250
+ return `(${this.json(field)})::numeric`
251
+ case 'Boolean':
252
+ return `(${this.string(field)})::bool`
253
+ case 'BigInt':
254
+ return `(${this.string(field)})::numeric`
255
+ case 'Bytes':
256
+ return `decode(substr(${this.string(field)}, 3), 'hex')`
257
+ case 'DateTime':
258
+ return `(${this.string(field)})::timestamptz`
259
+ default:
260
+ return this.string(field)
261
+ }
262
+ default:
263
+ throw unexpectedCase(prop.type.kind)
264
+ }
265
+ }
266
+
267
+ ref(field: string): string {
268
+ return this.json(field)
269
+ }
270
+
271
+ child(field: string): Cursor {
272
+ let prop = this.prop(field)
273
+ switch(prop.type.kind) {
274
+ case "object":
275
+ case "union":
276
+ return new ObjectCursor(
277
+ this.ctx,
278
+ this.json(field),
279
+ prop.type
280
+ )
281
+ case "fk":
282
+ return new EntityCursor(
283
+ this.ctx,
284
+ prop.type.foreignEntity,
285
+ {on: 'id', rhs: this.string(field)}
286
+ )
287
+ default:
288
+ throw unexpectedCase(prop.type.kind)
289
+ }
290
+ }
291
+ }
@@ -0,0 +1,66 @@
1
+ import {unexpectedCase} from "@subsquid/util-internal"
2
+ import {FieldRequest} from "../ir/fields"
3
+
4
+
5
+ export function mapRows(rows: any[][], fields: FieldRequest[]): any[] {
6
+ let result = new Array(rows.length)
7
+ for (let i = 0; i < rows.length; i++) {
8
+ result[i] = mapRow(rows[i], fields)
9
+ }
10
+ return result
11
+ }
12
+
13
+
14
+ export function mapRow(row: any[], fields: FieldRequest[], ifType?: string): any {
15
+ let rec: any = {}
16
+ for (let f of fields) {
17
+ if (f.ifType != ifType) continue
18
+ for (let alias of f.aliases) {
19
+ switch(f.kind) {
20
+ case "scalar":
21
+ case "enum":
22
+ case "list":
23
+ rec[alias] = row[f.index]
24
+ break
25
+ case "object": {
26
+ let isNull = row[f.index]
27
+ rec[alias] = isNull ? null : mapRow(row, f.children)
28
+ break
29
+ }
30
+ case "union": {
31
+ let isTypeOf = row[f.index]
32
+ if (isTypeOf) {
33
+ let obj = mapRow(row, f.children, isTypeOf)
34
+ obj.isTypeOf = isTypeOf
35
+ rec[alias] = obj
36
+ } else {
37
+ rec[alias] = null
38
+ }
39
+ break
40
+ }
41
+ case "fk":
42
+ case "lookup": {
43
+ let id = row[f.index]
44
+ if (id == null) {
45
+ rec[alias] = null
46
+ } else {
47
+ rec[alias] = mapRow(row, f.children)
48
+ }
49
+ break
50
+ }
51
+ case "list-lookup": {
52
+ let rows = row[f.index]
53
+ if (rows == null) {
54
+ rec[alias] = []
55
+ } else {
56
+ rec[alias] = mapRows(rows, f.children)
57
+ }
58
+ break
59
+ }
60
+ default:
61
+ throw unexpectedCase((f as any).kind)
62
+ }
63
+ }
64
+ }
65
+ return rec
66
+ }
@@ -0,0 +1,328 @@
1
+ import {unexpectedCase} from "@subsquid/util-internal"
2
+ import assert from "assert"
3
+ import {Dialect} from "../dialect"
4
+ import {EntityListArguments, OrderBy, Where} from "../ir/args"
5
+ import {FieldRequest} from "../ir/fields"
6
+ import {Model} from "../model"
7
+ import {Cursor, EntityCursor} from "./cursor"
8
+ import {AliasSet, ColumnSet, escapeIdentifier, JoinSet, printClause} from "./util"
9
+
10
+
11
+ export class EntityListQueryPrinter {
12
+ private aliases: AliasSet
13
+ private join: JoinSet
14
+ private root: EntityCursor
15
+ private columns = new ColumnSet()
16
+ private where: string[] = []
17
+ private orderBy: string[] = []
18
+
19
+ constructor(
20
+ private model: Model,
21
+ private dialect: Dialect,
22
+ private entityName: string,
23
+ private params: unknown[],
24
+ private args: EntityListArguments = {},
25
+ fields?: FieldRequest[],
26
+ aliases?: AliasSet
27
+ ) {
28
+ this.aliases = aliases || new AliasSet()
29
+ this.join = new JoinSet(this.aliases)
30
+ this.root = new EntityCursor(
31
+ {
32
+ model: this.model,
33
+ dialect: this.dialect,
34
+ aliases: this.aliases,
35
+ join: this.join
36
+ },
37
+ this.entityName
38
+ )
39
+ if (fields?.length) {
40
+ this.populateColumns(this.root, fields)
41
+ }
42
+ if (args.where) {
43
+ this.populateWhere(this.root, args.where, this.where)
44
+ }
45
+ if (args.orderBy) {
46
+ this.populateOrderBy(this.root, args.orderBy)
47
+ }
48
+ }
49
+
50
+ private sub(entityName: string, args?: EntityListArguments, fields?: FieldRequest[]): EntityListQueryPrinter {
51
+ return new EntityListQueryPrinter(this.model, this.dialect, entityName, this.params, args, fields, this.aliases)
52
+ }
53
+
54
+ private populateColumns(cursor: Cursor, fields: FieldRequest[]): void {
55
+ for (let f of fields) {
56
+ switch(f.kind) {
57
+ case "scalar":
58
+ case "enum":
59
+ case "list":
60
+ f.index = this.columns.add(cursor.output(f.field))
61
+ break
62
+ case "object":
63
+ f.index = this.columns.add(cursor.ref(f.field) + " IS NULL")
64
+ this.populateColumns(cursor.child(f.field), f.children)
65
+ break
66
+ case "union": {
67
+ let c = cursor.child(f.field)
68
+ f.index = this.columns.add(c.output("isTypeOf"))
69
+ this.populateColumns(c, f.children)
70
+ break
71
+ }
72
+ case "fk":
73
+ case "lookup": {
74
+ let c = cursor.child(f.field)
75
+ f.index = this.columns.add(c.output("id"))
76
+ this.populateColumns(c, f.children)
77
+ break
78
+ }
79
+ case "list-lookup": {
80
+ let sub = this.sub(f.type.entity, f.args, f.children).addWhereDerivedFrom(f.type.field, cursor.native("id"))
81
+ let exp = `(SELECT jsonb_agg(row) FROM (${sub.printAsJsonRows()}) AS rows)`
82
+ f.index = this.columns.add(exp)
83
+ break
84
+ }
85
+ default:
86
+ throw unexpectedCase()
87
+ }
88
+ }
89
+ }
90
+
91
+ private populateWhere(cursor: Cursor, where: Where, exps: string[]): void {
92
+ switch(where.op) {
93
+ case "AND":
94
+ for (let cond of where.args) {
95
+ this.populateWhere(cursor, cond, exps)
96
+ }
97
+ break
98
+ case "OR": {
99
+ let or: string[] = []
100
+ for (let cond of where.args) {
101
+ let exp = this.printWhere(cursor, cond)
102
+ if (exp) {
103
+ or.push("(" + exp + ")")
104
+ }
105
+ }
106
+ if (or.length > 0) {
107
+ exps.push("(" + printClause("OR", or) + ")")
108
+ }
109
+ break
110
+ }
111
+ case "REF":
112
+ this.populateWhere(cursor.child(where.field), where.where, exps)
113
+ break
114
+ case "in": {
115
+ let args = where.values.map(v => this.param(v))
116
+ if (args.length > 0) {
117
+ exps.push(`${cursor.native(where.field)} IN (${args.join(", ")})`)
118
+ } else {
119
+ exps.push("false")
120
+ }
121
+ break
122
+ }
123
+ case "not_in": {
124
+ let args = where.values.map(v => this.param(v))
125
+ if (args.length > 0) {
126
+ exps.push(`${cursor.native(where.field)} NOT IN (${args.join(", ")})`)
127
+ }
128
+ break
129
+ }
130
+ case "isNull": {
131
+ let f = cursor.ref(where.field)
132
+ if (where.yes) {
133
+ exps.push(`${f} IS NULL`)
134
+ } else {
135
+ exps.push(`${f} IS NOT NULL`)
136
+ }
137
+ break
138
+ }
139
+ case "eq":
140
+ this.scalarBinaryCondition("=", cursor, where, exps)
141
+ break
142
+ case "not_eq":
143
+ this.scalarBinaryCondition("!=", cursor, where, exps)
144
+ break
145
+ case "gt":
146
+ this.scalarBinaryCondition(">", cursor, where, exps)
147
+ break
148
+ case "gte":
149
+ this.scalarBinaryCondition(">=", cursor, where, exps)
150
+ break
151
+ case "lt":
152
+ this.scalarBinaryCondition("<", cursor, where, exps)
153
+ break
154
+ case "lte":
155
+ this.scalarBinaryCondition("<=", cursor, where, exps)
156
+ break
157
+ case "jsonContains":
158
+ this.scalarBinaryCondition("@>", cursor, where, exps)
159
+ break
160
+ case "jsonHasKey":
161
+ this.scalarBinaryCondition("?", cursor, where, exps)
162
+ break
163
+ case "containsAll":
164
+ this.refBinaryCondition("@>", cursor, where, exps)
165
+ break
166
+ case "containsAny":
167
+ this.refBinaryCondition("&&", cursor, where, exps)
168
+ break
169
+ case "containsNone": {
170
+ let lhs = cursor.ref(where.field)
171
+ let rhs = this.param(where.value)
172
+ exps.push(`NOT (${lhs} && ${rhs})`)
173
+ break
174
+ }
175
+ case "startsWith":
176
+ if (this.dialect == "cockroach") {
177
+ let f = cursor.native(where.field)
178
+ let p = this.param(where.value) + "::text"
179
+ exps.push(`${f} >= ${p}`)
180
+ exps.push(`left(${f}, length(${p})) = ${p}`)
181
+ } else {
182
+ exps.push(`starts_with(${cursor.native(where.field)}, ${this.param(where.value)})`)
183
+ }
184
+ break
185
+ case "not_startsWith":
186
+ if (this.dialect == "cockroach") {
187
+ let f = cursor.native(where.field)
188
+ let p = this.param(where.value) + "::text"
189
+ exps.push(`(${f} < ${p} OR left(${f}, length(${p})) != ${p})`)
190
+ } else {
191
+ exps.push(`NOT starts_with(${cursor.native(where.field)}, ${this.param(where.value)})`)
192
+ }
193
+ break
194
+ case "endsWith": {
195
+ let f = cursor.native(where.field)
196
+ let p = this.param(where.value) + "::text"
197
+ exps.push(`right(${f}, length(${p})) = ${p}`)
198
+ break
199
+ }
200
+ case "not_endsWith": {
201
+ let f = cursor.native(where.field)
202
+ let p = this.param(where.value) + "::text"
203
+ exps.push(`right(${f}, length(${p})) != ${p}`)
204
+ break
205
+ }
206
+ case "contains":
207
+ exps.push(`position(${this.param(where.value)} in ${cursor.native(where.field)}) > 0`)
208
+ break
209
+ case "not_contains":
210
+ exps.push(`position(${this.param(where.value)} in ${cursor.native(where.field)}) = 0`)
211
+ break
212
+ case "containsInsensitive":
213
+ exps.push(`position(lower(${this.param(where.value)}) in lower(${cursor.native(where.field)})) > 0`)
214
+ break
215
+ case "not_containsInsensitive":
216
+ exps.push(`position(lower(${this.param(where.value)}) in lower(${cursor.native(where.field)})) = 0`)
217
+ break
218
+ case "every": {
219
+ let rel = cursor.prop(where.field)
220
+ assert(rel.type.kind == "list-lookup")
221
+ let cond = this.sub(rel.type.entity, {where: where.where}).addWhereDerivedFrom(rel.type.field, cursor.native("id"))
222
+ let all = this.sub(rel.type.entity).addWhereDerivedFrom(rel.type.field, cursor.native("id"))
223
+ exps.push(`(SELECT count(*) ${cond.printFrom()}) = (SELECT count(*) ${all.printFrom()})`)
224
+ break
225
+ }
226
+ case "some":
227
+ case "none": {
228
+ let rel = cursor.prop(where.field)
229
+ assert(rel.type.kind == "list-lookup")
230
+ let sub = this.sub(rel.type.entity, {
231
+ where: where.where,
232
+ limit: 1
233
+ }).addWhereDerivedFrom(rel.type.field, cursor.native("id"))
234
+ if (where.op == "some") {
235
+ exps.push(`(SELECT true ${sub.printFrom()})`)
236
+ } else {
237
+ exps.push(`(SELECT count(*) FROM (SELECT true ${sub.printFrom()}) AS rows) = 0`)
238
+ }
239
+ break
240
+ }
241
+ default:
242
+ throw unexpectedCase((where as any).op)
243
+ }
244
+ }
245
+
246
+ private scalarBinaryCondition(sqlOp: string, cursor: Cursor, where: {field: string, value: unknown}, exps: string[]): void {
247
+ let f = cursor.native(where.field)
248
+ let p = this.param(where.value)
249
+ exps.push(`${f} ${sqlOp} ${p}`)
250
+ }
251
+
252
+ private refBinaryCondition(sqlOp: string, cursor: Cursor, where: {field: string, value: unknown}, exps: string[]): void {
253
+ let f = cursor.ref(where.field)
254
+ let p = this.param(where.value)
255
+ exps.push(`${f} ${sqlOp} ${p}`)
256
+ }
257
+
258
+ private printWhere(cursor: Cursor, where: Where): string {
259
+ let exps: string[] = []
260
+ this.populateWhere(cursor, where, exps)
261
+ return printClause("AND", exps)
262
+ }
263
+
264
+ private populateOrderBy(cursor: Cursor, orderBy: OrderBy): void {
265
+ for (let field in orderBy) {
266
+ let spec = orderBy[field]
267
+ if (typeof spec == "string") {
268
+ this.orderBy.push(`${cursor.native(field)} ${spec}`)
269
+ } else {
270
+ this.populateOrderBy(cursor.child(field), spec)
271
+ }
272
+ }
273
+ }
274
+
275
+ private param(value: any): string {
276
+ return "$" + this.params.push(value)
277
+ }
278
+
279
+ private ident(name: string): string {
280
+ return escapeIdentifier(this.dialect, name)
281
+ }
282
+
283
+ addWhereDerivedFrom(field: string, parentIdExp: string): this {
284
+ this.where.push(`${this.root.native(field)} = ${parentIdExp}`)
285
+ return this
286
+ }
287
+
288
+ printColumnList(options?: {withAliases?: boolean}): string {
289
+ let names = this.columns.names()
290
+ assert(names.length > 0)
291
+ if (options?.withAliases) {
292
+ names = names.map((name, idx) => `${name} AS _c${idx}`)
293
+ }
294
+ return names.join(', ')
295
+ }
296
+
297
+ printColumnListAsJsonArray(): string {
298
+ return `json_build_array(${this.printColumnList()})`
299
+ }
300
+
301
+ printFrom(): string {
302
+ let out = `FROM ${this.ident(this.root.table)} AS ${this.ident(this.root.tableAlias)}`
303
+ this.join.forEach(j => {
304
+ out += ` LEFT OUTER JOIN ${this.ident(j.table)} ${this.ident(j.alias)} ON ${this.ident(j.alias)}.${this.ident(j.column)} = ${j.rhs}`
305
+ })
306
+ if (this.where.length > 0) {
307
+ out += ` WHERE ${printClause("AND", this.where)}`
308
+ }
309
+ if (this.orderBy.length > 0) {
310
+ out += " ORDER BY " + this.orderBy.join(", ")
311
+ }
312
+ if (this.args.limit != null) {
313
+ out += ` LIMIT ${this.args.limit}`
314
+ }
315
+ if (this.args.offset) {
316
+ out += ` OFFSET ${this.args.offset}`
317
+ }
318
+ return out
319
+ }
320
+
321
+ print(): string {
322
+ return `SELECT ${this.printColumnList({withAliases: true})} ${this.printFrom()}`
323
+ }
324
+
325
+ printAsJsonRows(): string {
326
+ return `SELECT ${this.printColumnListAsJsonArray()} AS row ${this.printFrom()}`
327
+ }
328
+ }