db.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. import { entityKind } from "../entity.js";
  2. import {
  3. GelDeleteBase,
  4. GelInsertBuilder,
  5. GelSelectBuilder,
  6. GelUpdateBuilder,
  7. QueryBuilder
  8. } from "./query-builders/index.js";
  9. import { SelectionProxyHandler } from "../selection-proxy.js";
  10. import { sql } from "../sql/sql.js";
  11. import { WithSubquery } from "../subquery.js";
  12. import { GelCountBuilder } from "./query-builders/count.js";
  13. import { RelationalQueryBuilder } from "./query-builders/query.js";
  14. import { GelRaw } from "./query-builders/raw.js";
  15. class GelDatabase {
  16. constructor(dialect, session, schema) {
  17. this.dialect = dialect;
  18. this.session = session;
  19. this._ = schema ? {
  20. schema: schema.schema,
  21. fullSchema: schema.fullSchema,
  22. tableNamesMap: schema.tableNamesMap,
  23. session
  24. } : {
  25. schema: void 0,
  26. fullSchema: {},
  27. tableNamesMap: {},
  28. session
  29. };
  30. this.query = {};
  31. if (this._.schema) {
  32. for (const [tableName, columns] of Object.entries(this._.schema)) {
  33. this.query[tableName] = new RelationalQueryBuilder(
  34. schema.fullSchema,
  35. this._.schema,
  36. this._.tableNamesMap,
  37. schema.fullSchema[tableName],
  38. columns,
  39. dialect,
  40. session
  41. );
  42. }
  43. }
  44. this.$cache = { invalidate: async (_params) => {
  45. } };
  46. }
  47. static [entityKind] = "GelDatabase";
  48. query;
  49. /**
  50. * Creates a subquery that defines a temporary named result set as a CTE.
  51. *
  52. * It is useful for breaking down complex queries into simpler parts and for reusing the result set in subsequent parts of the query.
  53. *
  54. * See docs: {@link https://orm.drizzle.team/docs/select#with-clause}
  55. *
  56. * @param alias The alias for the subquery.
  57. *
  58. * Failure to provide an alias will result in a DrizzleTypeError, preventing the subquery from being referenced in other queries.
  59. *
  60. * @example
  61. *
  62. * ```ts
  63. * // Create a subquery with alias 'sq' and use it in the select query
  64. * const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42)));
  65. *
  66. * const result = await db.with(sq).select().from(sq);
  67. * ```
  68. *
  69. * To select arbitrary SQL values as fields in a CTE and reference them in other CTEs or in the main query, you need to add aliases to them:
  70. *
  71. * ```ts
  72. * // Select an arbitrary SQL value as a field in a CTE and reference it in the main query
  73. * const sq = db.$with('sq').as(db.select({
  74. * name: sql<string>`upper(${users.name})`.as('name'),
  75. * })
  76. * .from(users));
  77. *
  78. * const result = await db.with(sq).select({ name: sq.name }).from(sq);
  79. * ```
  80. */
  81. $with(alias) {
  82. const self = this;
  83. return {
  84. as(qb) {
  85. if (typeof qb === "function") {
  86. qb = qb(new QueryBuilder(self.dialect));
  87. }
  88. return new Proxy(
  89. new WithSubquery(qb.getSQL(), qb.getSelectedFields(), alias, true),
  90. new SelectionProxyHandler({ alias, sqlAliasedBehavior: "alias", sqlBehavior: "error" })
  91. );
  92. }
  93. };
  94. }
  95. $count(source, filters) {
  96. return new GelCountBuilder({ source, filters, session: this.session });
  97. }
  98. /**
  99. * Incorporates a previously defined CTE (using `$with`) into the main query.
  100. *
  101. * This method allows the main query to reference a temporary named result set.
  102. *
  103. * See docs: {@link https://orm.drizzle.team/docs/select#with-clause}
  104. *
  105. * @param queries The CTEs to incorporate into the main query.
  106. *
  107. * @example
  108. *
  109. * ```ts
  110. * // Define a subquery 'sq' as a CTE using $with
  111. * const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42)));
  112. *
  113. * // Incorporate the CTE 'sq' into the main query and select from it
  114. * const result = await db.with(sq).select().from(sq);
  115. * ```
  116. */
  117. with(...queries) {
  118. const self = this;
  119. function select(fields) {
  120. return new GelSelectBuilder({
  121. fields: fields ?? void 0,
  122. session: self.session,
  123. dialect: self.dialect,
  124. withList: queries
  125. });
  126. }
  127. function selectDistinct(fields) {
  128. return new GelSelectBuilder({
  129. fields: fields ?? void 0,
  130. session: self.session,
  131. dialect: self.dialect,
  132. withList: queries,
  133. distinct: true
  134. });
  135. }
  136. function selectDistinctOn(on, fields) {
  137. return new GelSelectBuilder({
  138. fields: fields ?? void 0,
  139. session: self.session,
  140. dialect: self.dialect,
  141. withList: queries,
  142. distinct: { on }
  143. });
  144. }
  145. function update(table) {
  146. return new GelUpdateBuilder(table, self.session, self.dialect, queries);
  147. }
  148. function insert(table) {
  149. return new GelInsertBuilder(table, self.session, self.dialect, queries);
  150. }
  151. function delete_(table) {
  152. return new GelDeleteBase(table, self.session, self.dialect, queries);
  153. }
  154. return { select, selectDistinct, selectDistinctOn, update, insert, delete: delete_ };
  155. }
  156. select(fields) {
  157. return new GelSelectBuilder({
  158. fields: fields ?? void 0,
  159. session: this.session,
  160. dialect: this.dialect
  161. });
  162. }
  163. selectDistinct(fields) {
  164. return new GelSelectBuilder({
  165. fields: fields ?? void 0,
  166. session: this.session,
  167. dialect: this.dialect,
  168. distinct: true
  169. });
  170. }
  171. selectDistinctOn(on, fields) {
  172. return new GelSelectBuilder({
  173. fields: fields ?? void 0,
  174. session: this.session,
  175. dialect: this.dialect,
  176. distinct: { on }
  177. });
  178. }
  179. $cache;
  180. /**
  181. * Creates an update query.
  182. *
  183. * Calling this method without `.where()` clause will update all rows in a table. The `.where()` clause specifies which rows should be updated.
  184. *
  185. * Use `.set()` method to specify which values to update.
  186. *
  187. * See docs: {@link https://orm.drizzle.team/docs/update}
  188. *
  189. * @param table The table to update.
  190. *
  191. * @example
  192. *
  193. * ```ts
  194. * // Update all rows in the 'cars' table
  195. * await db.update(cars).set({ color: 'red' });
  196. *
  197. * // Update rows with filters and conditions
  198. * await db.update(cars).set({ color: 'red' }).where(eq(cars.brand, 'BMW'));
  199. *
  200. * // Update with returning clause
  201. * const updatedCar: Car[] = await db.update(cars)
  202. * .set({ color: 'red' })
  203. * .where(eq(cars.id, 1))
  204. * .returning();
  205. * ```
  206. */
  207. update(table) {
  208. return new GelUpdateBuilder(table, this.session, this.dialect);
  209. }
  210. /**
  211. * Creates an insert query.
  212. *
  213. * Calling this method will create new rows in a table. Use `.values()` method to specify which values to insert.
  214. *
  215. * See docs: {@link https://orm.drizzle.team/docs/insert}
  216. *
  217. * @param table The table to insert into.
  218. *
  219. * @example
  220. *
  221. * ```ts
  222. * // Insert one row
  223. * await db.insert(cars).values({ brand: 'BMW' });
  224. *
  225. * // Insert multiple rows
  226. * await db.insert(cars).values([{ brand: 'BMW' }, { brand: 'Porsche' }]);
  227. *
  228. * // Insert with returning clause
  229. * const insertedCar: Car[] = await db.insert(cars)
  230. * .values({ brand: 'BMW' })
  231. * .returning();
  232. * ```
  233. */
  234. insert(table) {
  235. return new GelInsertBuilder(table, this.session, this.dialect);
  236. }
  237. /**
  238. * Creates a delete query.
  239. *
  240. * Calling this method without `.where()` clause will delete all rows in a table. The `.where()` clause specifies which rows should be deleted.
  241. *
  242. * See docs: {@link https://orm.drizzle.team/docs/delete}
  243. *
  244. * @param table The table to delete from.
  245. *
  246. * @example
  247. *
  248. * ```ts
  249. * // Delete all rows in the 'cars' table
  250. * await db.delete(cars);
  251. *
  252. * // Delete rows with filters and conditions
  253. * await db.delete(cars).where(eq(cars.color, 'green'));
  254. *
  255. * // Delete with returning clause
  256. * const deletedCar: Car[] = await db.delete(cars)
  257. * .where(eq(cars.id, 1))
  258. * .returning();
  259. * ```
  260. */
  261. delete(table) {
  262. return new GelDeleteBase(table, this.session, this.dialect);
  263. }
  264. // TODO views are not implemented
  265. // refreshMaterializedView<TView extends GelMaterializedView>(view: TView): GelRefreshMaterializedView<TQueryResult> {
  266. // return new GelRefreshMaterializedView(view, this.session, this.dialect);
  267. // }
  268. execute(query) {
  269. const sequel = typeof query === "string" ? sql.raw(query) : query.getSQL();
  270. const builtQuery = this.dialect.sqlToQuery(sequel);
  271. const prepared = this.session.prepareQuery(
  272. builtQuery,
  273. void 0,
  274. void 0,
  275. false
  276. );
  277. return new GelRaw(
  278. () => prepared.execute(void 0),
  279. sequel,
  280. builtQuery,
  281. (result) => prepared.mapResult(result, true)
  282. );
  283. }
  284. transaction(transaction) {
  285. return this.session.transaction(transaction);
  286. }
  287. }
  288. const withReplicas = (primary, replicas, getReplica = () => replicas[Math.floor(Math.random() * replicas.length)]) => {
  289. const select = (...args) => getReplica(replicas).select(...args);
  290. const selectDistinct = (...args) => getReplica(replicas).selectDistinct(...args);
  291. const selectDistinctOn = (...args) => getReplica(replicas).selectDistinctOn(...args);
  292. const _with = (...args) => getReplica(replicas).with(...args);
  293. const $with = (arg) => getReplica(replicas).$with(arg);
  294. const update = (...args) => primary.update(...args);
  295. const insert = (...args) => primary.insert(...args);
  296. const $delete = (...args) => primary.delete(...args);
  297. const execute = (...args) => primary.execute(...args);
  298. const transaction = (...args) => primary.transaction(...args);
  299. return {
  300. ...primary,
  301. update,
  302. insert,
  303. delete: $delete,
  304. execute,
  305. transaction,
  306. // refreshMaterializedView,
  307. $primary: primary,
  308. $replicas: replicas,
  309. select,
  310. selectDistinct,
  311. selectDistinctOn,
  312. $with,
  313. with: _with,
  314. get query() {
  315. return getReplica(replicas).query;
  316. }
  317. };
  318. };
  319. export {
  320. GelDatabase,
  321. withReplicas
  322. };
  323. //# sourceMappingURL=db.js.map