Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: sailshq/waterline-sql-builder
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master@{2018-03-29}
Choose a base ref
...
head repository: sailshq/waterline-sql-builder
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref

Commits on Sep 29, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    a692899 View commit details
  2. Copy the full SHA
    3143cd7 View commit details
  3. Copy the full SHA
    d6b9163 View commit details
  4. modified update tests, since parameters and bindings returned by kn…

    …ex have reveresed order
    HPEnvy\Ryan committed Sep 29, 2020
    Copy the full SHA
    a7b3972 View commit details
  5. explicitly using 'oracledb' dialect in tests, instead of deprecated '…

    …oracle' value
    HPEnvy\Ryan committed Sep 29, 2020
    Copy the full SHA
    caca190 View commit details

Commits on Jul 7, 2021

  1. Update sequelizer.js

    eashaw committed Jul 7, 2021
    Copy the full SHA
    5e8a857 View commit details

Commits on Jul 20, 2021

  1. Copy the full SHA
    1715249 View commit details
  2. Copy the full SHA
    65e40b2 View commit details
  3. Copy the full SHA
    fc21565 View commit details
  4. make >0 check explicit

    mikermcneil committed Jul 20, 2021
    Copy the full SHA
    9d057b1 View commit details
  5. Copy the full SHA
    b4a13b0 View commit details
  6. Merge pull request #34 from sailshq/patch-2021-07-20

    Expand comments
    mikermcneil authored Jul 20, 2021
    Copy the full SHA
    a1eb1fe View commit details

Commits on Aug 2, 2021

  1. Update sequelizer.js

    eashaw committed Aug 2, 2021
    Copy the full SHA
    bc146bc View commit details
  2. Copy the full SHA
    af6fa6c View commit details

Commits on Aug 3, 2021

  1. Update sequelizer.js

    eashaw committed Aug 3, 2021
    Copy the full SHA
    fc73b5a View commit details
  2. Update sequelizer.js

    eashaw committed Aug 3, 2021
    Copy the full SHA
    e5c357a View commit details
  3. Update .travis.yml

    eashaw committed Aug 3, 2021
    Copy the full SHA
    be84d62 View commit details
  4. Merge pull request #36 from sailshq/revert-knexQuery-change

    fix bug introduced in previous commits since last release + update travis.yml
    eashaw authored Aug 3, 2021
    Copy the full SHA
    843c234 View commit details
  5. Copy the full SHA
    b7477a6 View commit details
  6. Merge pull request #35 from sailshq/checkForModifiers-fix

    Adjust checkForModifiers function to fix issue where criteria is being stripped out
    eashaw authored Aug 3, 2021
    Copy the full SHA
    f641138 View commit details
  7. 2.0.0

    eashaw committed Aug 3, 2021
    Copy the full SHA
    17dd2e3 View commit details

Commits on Jul 8, 2022

  1. Copy the full SHA
    e68e88a View commit details
  2. Copy the full SHA
    96217b5 View commit details
  3. Copy the full SHA
    e0efac0 View commit details
  4. Merge pull request #33 from carpiediem/knex-upgrade

    Knex upgrade
    eashaw authored Jul 8, 2022
    Copy the full SHA
    3008992 View commit details
  5. 3.0.0

    eashaw committed Jul 8, 2022
    Copy the full SHA
    dc07d4e View commit details

Commits on Mar 17, 2023

  1. upgrade knex

    eashaw committed Mar 17, 2023
    Copy the full SHA
    495a71f View commit details
  2. Copy the full SHA
    d74b650 View commit details
  3. Copy the full SHA
    a7217b6 View commit details

Commits on Mar 18, 2023

  1. Revert "test commit to (attempt to) trigger travis test"

    This reverts commit a7217b6.
    eashaw committed Mar 18, 2023
    Copy the full SHA
    2e6c0da View commit details

Commits on Mar 20, 2023

  1. Merge pull request #38 from sailshq/upgrade-knex-to-2.4.2

    Upgrade Knex to 2.4.2
    eashaw authored Mar 20, 2023
    Copy the full SHA
    8274444 View commit details
  2. 3.0.1

    eashaw committed Mar 20, 2023
    Copy the full SHA
    c866595 View commit details
Showing with 172 additions and 157 deletions.
  1. +3 −4 .travis.yml
  2. +9 −1 index.js
  3. +16 −8 lib/sequelizer.js
  4. +2 −2 package.json
  5. +4 −4 test/unit/generate/and.test.js
  6. +2 −2 test/unit/generate/avg.test.js
  7. +2 −2 test/unit/generate/count.test.js
  8. +2 −2 test/unit/generate/crossJoin.test.js
  9. +2 −2 test/unit/generate/delete.test.js
  10. +2 −2 test/unit/generate/distinct.test.js
  11. +2 −2 test/unit/generate/from.test.js
  12. +2 −2 test/unit/generate/fullOuterJoin.test.js
  13. +2 −2 test/unit/generate/groupBy.test.js
  14. +2 −2 test/unit/generate/innerJoin.test.js
  15. +6 −6 test/unit/generate/insert.test.js
  16. +6 −6 test/unit/generate/join.test.js
  17. +2 −2 test/unit/generate/leftJoin.test.js
  18. +2 −2 test/unit/generate/leftOuterJoin.test.js
  19. +2 −2 test/unit/generate/like.test.js
  20. +2 −2 test/unit/generate/limit.test.js
  21. +2 −2 test/unit/generate/null.test.js
  22. +4 −4 test/unit/generate/or.test.js
  23. +2 −2 test/unit/generate/orderBy.test.js
  24. +2 −2 test/unit/generate/outerJoin.test.js
  25. +11 −11 test/unit/generate/returning.test.js
  26. +2 −2 test/unit/generate/rightJoin.test.js
  27. +2 −2 test/unit/generate/rightOuterJoin.test.js
  28. +2 −2 test/unit/generate/schema.test.js
  29. +6 −6 test/unit/generate/select.test.js
  30. +2 −2 test/unit/generate/skip.test.js
  31. +4 −4 test/unit/generate/subquery.predicate.test.js
  32. +2 −2 test/unit/generate/subquery.scalar.test.js
  33. +2 −2 test/unit/generate/subquery.table.test.js
  34. +2 −2 test/unit/generate/sum.test.js
  35. +5 −5 test/unit/generate/union.test.js
  36. +5 −5 test/unit/generate/unionAll.test.js
  37. +15 −15 test/unit/generate/update.test.js
  38. +4 −4 test/unit/generate/where.in.test.js
  39. +6 −6 test/unit/generate/where.not.in.test.js
  40. +8 −8 test/unit/generate/where.not.test.js
  41. +8 −8 test/unit/generate/where.simple.test.js
  42. +1 −1 test/unit/sequelizer/sequelizer.union.test.js
  43. +1 −1 test/unit/sequelizer/sequelizer.unionAll.test.js
  44. +2 −2 test/unit/sequelizer/sequelizer.update.test.js
7 changes: 3 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -12,10 +12,9 @@
language: node_js

node_js:
- "4"
- "6"
- "7"
- "node"
- "12"
- "14"
- "16"

branches:
only:
10 changes: 9 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -24,9 +24,17 @@ module.exports = function sqlBuilder(options) {
throw new Error('Missing Dialect!');
}

// Modify deprecated dialect values
if (options.dialect==='mariadb') {
options.dialect='mysql';
}
if (options.dialect==='oracle') {
options.dialect='oracledb';
}

// Build up a Knex instance to use in the query builder
var knexInstance = Knex({
dialect: options.dialect,
client: options.dialect,
useNullAsDefault: true
});

24 changes: 16 additions & 8 deletions lib/sequelizer.js
Original file line number Diff line number Diff line change
@@ -57,6 +57,11 @@ module.exports = function sequelizer(options) {
//
// Check for any embedded combinators (OR) or modifiers (NOT) in a single
// expression set.
/**
* @returns {Dictionary}
* .combinator {String?} (undefined, "AND", or "OR")
* .modifier {Array}
**/
var checkForModifiers = function checkForModifiers(expr, options) {
var combinator;
var modifiers = [];
@@ -72,7 +77,7 @@ module.exports = function sequelizer(options) {
// Check for any encoded combinators and remove them
(function checkForAnd() {
var cIdx = _.indexOf(expr, 'AND');
if (cIdx > -1) {
if (cIdx === 0) {
combinator = 'AND';
if (options.strip && (options.strip === '*' || _.indexOf(options.strip, 'AND') > -1)) {
_.pullAt(expr, cIdx);
@@ -82,7 +87,7 @@ module.exports = function sequelizer(options) {

(function checkForOr() {
var cIdx = _.indexOf(expr, 'OR');
if (cIdx > -1) {
if (cIdx === 0) {
combinator = 'OR';
if (options.strip && (options.strip === '*' || _.indexOf(options.strip, 'OR') > -1)) {
_.pullAt(expr, cIdx);
@@ -94,7 +99,7 @@ module.exports = function sequelizer(options) {
// These represent things like NOT. Pull the value from the expression
(function checkForNot() {
var mIdx = _.indexOf(expr, 'NOT');
if (mIdx > -1) {
if (mIdx === 0) {
modifiers.push('NOT');
if (options.strip && (options.strip === '*' || _.indexOf(options.strip, 'NOT') > -1)) {
_.pullAt(expr, mIdx);
@@ -104,7 +109,7 @@ module.exports = function sequelizer(options) {

(function checkForIn() {
var mIdx = _.indexOf(expr, 'IN');
if (mIdx > -1) {
if (mIdx === 0) {
modifiers.push('IN');
if (options.strip && (options.strip === '*' || _.indexOf(options.strip, 'IN') > -1)) {
_.pullAt(expr, mIdx);
@@ -114,7 +119,7 @@ module.exports = function sequelizer(options) {

(function checkForNotIn() {
var mIdx = _.indexOf(expr, 'NOTIN');
if (mIdx > -1) {
if (mIdx === 0) {
modifiers.push('NOTIN');
if (options.strip && (options.strip === '*' || _.indexOf(options.strip, 'NOTIN') > -1)) {
_.pullAt(expr, mIdx);
@@ -160,12 +165,15 @@ module.exports = function sequelizer(options) {
}

// Build a function that when called, creates a nested grouping of statements.
query[fn].call(query, function buildGroupFn() {
query[fn].call(query, function () {// »build group fn
var self = this;

// ╔╦╗╔═╗╔╦╗╔═╗╦═╗╔╦╗╦╔╗╔╔═╗ ┬┌─┌┐┌┌─┐─┐ ┬ ┌─┐┬ ┬┌┐┌┌─┐┌┬┐┬┌─┐┌┐┌
// ║║║╣ ║ ║╣ ╠╦╝║║║║║║║║╣ ├┴┐│││├┤ ┌┴┬┘ ├┤ │ │││││ │ ││ ││││
// ═╩╝╚═╝ ╩ ╚═╝╩╚═╩ ╩╩╝╚╝╚═╝ ┴ ┴┘└┘└─┘┴ └─ └ └─┘┘└┘└─┘ ┴ ┴└─┘┘└┘
/**
* @returns {String}
*/
var determineFn = function determineFn(_expr, idx) {
// default the _fn to `orWhere`
var _fn = 'orWhere';
@@ -282,7 +290,7 @@ module.exports = function sequelizer(options) {
// ╦╔═╦╔═╗╦╔═ ┌─┐┌─┐┌─┐ ┬─┐┌─┐┌─┐┬ ┬┬─┐┌─┐┬┌─┐┌┐┌
// ╠╩╗║║ ╠╩╗ │ │├┤ ├┤ ├┬┘├┤ │ │ │├┬┘└─┐││ ││││
// ╩ ╩╩╚═╝╩ ╩ └─┘└ └ ┴└─└─┘└─┘└─┘┴└─└─┘┴└─┘┘└┘
_.each(_exprGroup, function processGroups(_expr, idx) {
_.each(_exprGroup, function (_expr, idx) {
var _fn = determineFn(_expr, idx);
var args = recurse(_expr);

@@ -528,7 +536,7 @@ module.exports = function sequelizer(options) {

// Check the modifier to see if a different function other than
// OR WHERE should be used. The most common is OR WHERE NOT IN.
if (modifiers.modifier.length) {
if (modifiers.modifier.length > 0) {
if (modifiers.modifier.length === 1) {
if (_.first(modifiers.modifier) === 'NOT') {
fn = fn === 'orWhere' ? 'orWhereNot' : 'whereNot';
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "waterline-sql-builder",
"version": "1.0.0",
"version": "3.0.1",
"description": "Generate SQL (stage 5 query) from a Waterline statement (stage 4 query).",
"scripts": {
"test": "node ./node_modules/mocha/bin/mocha test/unit --recursive",
@@ -26,7 +26,7 @@
],
"license": "MIT",
"dependencies": {
"knex": "0.12.7",
"knex": "2.4.2",
"@sailshq/lodash": "^3.10.2",
"waterline-utils": "^1.3.8"
},
8 changes: 4 additions & 4 deletions test/unit/generate/and.test.js
Original file line number Diff line number Diff line change
@@ -31,11 +31,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'select * from "users" where "firstName" = ? and "lastName" = ?',
sql: 'select * from `users` where `firstName` = ? and `lastName` = ?',
bindings: ['foo', 'bar']
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'select * from "users" where "firstName" = :1 and "lastName" = :2',
bindings: ['foo', 'bar']
},
@@ -95,11 +95,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'select * from "users" where ("firstName" = ? or "lastName" = ?) and ("qty" > ? or "price" < ?)',
sql: 'select * from `users` where (`firstName` = ? or `lastName` = ?) and (`qty` > ? or `price` < ?)',
bindings: ['John', 'Smith', '100', '10.01']
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'select * from "users" where ("firstName" = :1 or "lastName" = :2) and ("qty" > :3 or "price" < :4)',
bindings: ['John', 'Smith', '100', '10.01']
},
4 changes: 2 additions & 2 deletions test/unit/generate/avg.test.js
Original file line number Diff line number Diff line change
@@ -21,11 +21,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'select avg("active") from "users"',
sql: 'select avg(`active`) from `users`',
bindings: []
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'select avg("active") from "users"',
bindings: []
},
4 changes: 2 additions & 2 deletions test/unit/generate/count.test.js
Original file line number Diff line number Diff line change
@@ -21,11 +21,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'select count(*) from "users"',
sql: 'select count(*) from `users`',
bindings: []
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'select count(*) from "users"',
bindings: []
},
4 changes: 2 additions & 2 deletions test/unit/generate/crossJoin.test.js
Original file line number Diff line number Diff line change
@@ -30,11 +30,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'select "users"."id", "contacts"."phone" from "users" cross join "contacts" on "users"."id" = "contacts"."user_id"',
sql: 'select `users`.`id`, `contacts`.`phone` from `users` cross join `contacts` on `users`.`id` = `contacts`.`user_id`',
bindings: []
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'select "users"."id", "contacts"."phone" from "users" cross join "contacts" on "users"."id" = "contacts"."user_id"',
bindings: []
},
4 changes: 2 additions & 2 deletions test/unit/generate/delete.test.js
Original file line number Diff line number Diff line change
@@ -28,11 +28,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'delete from "accounts" where "activated" = ?',
sql: 'delete from `accounts` where `activated` = ?',
bindings: [false]
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'delete from "accounts" where "activated" = :1',
bindings: ['0']
},
4 changes: 2 additions & 2 deletions test/unit/generate/distinct.test.js
Original file line number Diff line number Diff line change
@@ -23,11 +23,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'select distinct "firstName", "lastName" from "customers"',
sql: 'select distinct `firstName`, `lastName` from `customers`',
bindings: []
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'select distinct "firstName", "lastName" from "customers"',
bindings: []
},
4 changes: 2 additions & 2 deletions test/unit/generate/from.test.js
Original file line number Diff line number Diff line change
@@ -21,11 +21,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'select * from "books"',
sql: 'select * from `books`',
bindings: []
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'select * from "books"',
bindings: []
},
4 changes: 2 additions & 2 deletions test/unit/generate/fullOuterJoin.test.js
Original file line number Diff line number Diff line change
@@ -30,11 +30,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'select "users"."id", "contacts"."phone" from "users" full outer join "contacts" on "users"."id" = "contacts"."user_id"',
sql: 'select `users`.`id`, `contacts`.`phone` from `users` full outer join `contacts` on `users`.`id` = `contacts`.`user_id`',
bindings: []
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'select "users"."id", "contacts"."phone" from "users" full outer join "contacts" on "users"."id" = "contacts"."user_id"',
bindings: []
},
4 changes: 2 additions & 2 deletions test/unit/generate/groupBy.test.js
Original file line number Diff line number Diff line change
@@ -22,11 +22,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'select * from "users" group by "count"',
sql: 'select * from `users` group by `count`',
bindings: []
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'select * from "users" group by "count"',
bindings: []
},
4 changes: 2 additions & 2 deletions test/unit/generate/innerJoin.test.js
Original file line number Diff line number Diff line change
@@ -30,11 +30,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'select "users"."id", "contacts"."phone" from "users" inner join "contacts" on "users"."id" = "contacts"."user_id"',
sql: 'select `users`.`id`, `contacts`.`phone` from `users` inner join `contacts` on `users`.`id` = `contacts`.`user_id`',
bindings: []
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'select "users"."id", "contacts"."phone" from "users" inner join "contacts" on "users"."id" = "contacts"."user_id"',
bindings: []
},
12 changes: 6 additions & 6 deletions test/unit/generate/insert.test.js
Original file line number Diff line number Diff line change
@@ -23,11 +23,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'insert into "books" ("title") values (?)',
sql: 'insert into `books` (`title`) values (?)',
bindings: ['Slaughterhouse Five']
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'insert into "books" ("title") values (:1)',
bindings: ['Slaughterhouse Five']
},
@@ -62,11 +62,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'insert into "books" ("author", "title") values (?, ?)',
sql: 'insert into `books` (`author`, `title`) values (?, ?)',
bindings: ['Kurt Vonnegut', 'Slaughterhouse Five']
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'insert into "books" ("author", "title") values (:1, :2)',
bindings: ['Kurt Vonnegut', 'Slaughterhouse Five']
},
@@ -107,11 +107,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'insert into "books" ("author", "title") select ? as "author", ? as "title" union all select ? as "author", ? as "title"',
sql: 'insert into `books` (`author`, `title`) select ? as `author`, ? as `title` union all select ? as `author`, ? as `title`',
bindings: ['Kurt Vonnegut', 'Slaughterhouse Five', 'F. Scott Fitzgerald', 'The Great Gatsby']
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'begin execute immediate \'insert into "books" ("author", "title") values (:1, :2)\' using :1, :2; execute immediate \'insert into "books" ("author", "title") values (:1, :2)\' using :3, :4;end;',
bindings: ['Kurt Vonnegut', 'Slaughterhouse Five', 'F. Scott Fitzgerald', 'The Great Gatsby']
},
12 changes: 6 additions & 6 deletions test/unit/generate/join.test.js
Original file line number Diff line number Diff line change
@@ -30,11 +30,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'select "users"."id", "contacts"."phone" from "users" inner join "contacts" on "users"."id" = "contacts"."user_id"',
sql: 'select `users`.`id`, `contacts`.`phone` from `users` inner join `contacts` on `users`.`id` = `contacts`.`user_id`',
bindings: []
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'select "users"."id", "contacts"."phone" from "users" inner join "contacts" on "users"."id" = "contacts"."user_id"',
bindings: []
},
@@ -82,11 +82,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'select "users"."id", "contacts"."phone", "carriers"."name" from "users" inner join "contacts" on "users"."id" = "contacts"."user_id" inner join "carriers" on "users"."id" = "carriers"."user_id"',
sql: 'select `users`.`id`, `contacts`.`phone`, `carriers`.`name` from `users` inner join `contacts` on `users`.`id` = `contacts`.`user_id` inner join `carriers` on `users`.`id` = `carriers`.`user_id`',
bindings: []
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'select "users"."id", "contacts"."phone", "carriers"."name" from "users" inner join "contacts" on "users"."id" = "contacts"."user_id" inner join "carriers" on "users"."id" = "carriers"."user_id"',
bindings: []
},
@@ -135,11 +135,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'select * from "users" inner join "accounts" on "accounts"."id" = "users"."account_id" or "accounts"."owner_id" = "users"."id"',
sql: 'select * from `users` inner join `accounts` on `accounts`.`id` = `users`.`account_id` or `accounts`.`owner_id` = `users`.`id`',
bindings: []
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'select * from "users" inner join "accounts" on "accounts"."id" = "users"."account_id" or "accounts"."owner_id" = "users"."id"',
bindings: []
},
4 changes: 2 additions & 2 deletions test/unit/generate/leftJoin.test.js
Original file line number Diff line number Diff line change
@@ -30,11 +30,11 @@ describe('Query Generation ::', function() {
},
{
dialect: 'sqlite3',
sql: 'select "users"."id", "contacts"."phone" from "users" left join "contacts" on "users"."id" = "contacts"."user_id"',
sql: 'select `users`.`id`, `contacts`.`phone` from `users` left join `contacts` on `users`.`id` = `contacts`.`user_id`',
bindings: []
},
{
dialect: 'oracle',
dialect: 'oracledb',
sql: 'select "users"."id", "contacts"."phone" from "users" left join "contacts" on "users"."id" = "contacts"."user_id"',
bindings: []
},
Loading