@uwdata/mosaic-sql 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2023, UW Interactive Data Lab
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md CHANGED
@@ -1,3 +1,3 @@
1
- # @mosaic/sql
1
+ # mosaic-sql
2
2
 
3
3
  An API for convenient construction and analysis of SQL queries. Query objects then coerce to SQL query strings.
@@ -37,34 +37,6 @@ function column(table, column2) {
37
37
  function all(table) {
38
38
  return new Ref(table, "*");
39
39
  }
40
- function desc(expr2) {
41
- expr2 = asColumn(expr2);
42
- return {
43
- expr: expr2,
44
- desc: true,
45
- toString: () => `${expr2} DESC NULLS LAST`,
46
- get columns() {
47
- return expr2.columns?.() || [];
48
- }
49
- };
50
- }
51
- function expr(sql, columns, label) {
52
- return {
53
- expr: sql,
54
- label,
55
- toString: () => `${sql}`,
56
- get columns() {
57
- return columns || [];
58
- }
59
- };
60
- }
61
- function transform(func2, label) {
62
- return (value) => expr(
63
- func2(value),
64
- asColumn(value).columns,
65
- label
66
- );
67
- }
68
40
 
69
41
  // src/to-sql.js
70
42
  function toSQL(value) {
@@ -89,6 +61,87 @@ function literalToSQL(value) {
89
61
  }
90
62
  }
91
63
 
64
+ // src/expression.js
65
+ var isParamLike = (e) => typeof e?.addEventListener === "function";
66
+ function isExpression(e) {
67
+ return e instanceof SQLExpression;
68
+ }
69
+ var SQLExpression = class {
70
+ constructor(sql2, columns, label) {
71
+ this.expr = sql2;
72
+ this.label = label;
73
+ this.columns = columns || [];
74
+ }
75
+ toString() {
76
+ return `${this.expr}`;
77
+ }
78
+ };
79
+ var ParameterizedSQLExpression = class extends SQLExpression {
80
+ constructor(parts, columns, label) {
81
+ const paramSet = /* @__PURE__ */ new Set();
82
+ for (const part of parts) {
83
+ if (isParamLike(part))
84
+ paramSet.add(part);
85
+ }
86
+ paramSet.forEach((param) => {
87
+ param.addEventListener("value", () => this.update());
88
+ });
89
+ super(parts, columns, label);
90
+ }
91
+ toString() {
92
+ return this.expr.map((p) => isParamLike(p) ? literalToSQL(p.value) : p).join("");
93
+ }
94
+ addEventListener(type, callback) {
95
+ const map = this.map || (this.map = /* @__PURE__ */ new Map());
96
+ const set = map.get(type) || (map.set(type, /* @__PURE__ */ new Set()), map.get(type));
97
+ set.add(callback);
98
+ }
99
+ update() {
100
+ this.map?.get("value")?.forEach((callback) => callback(this));
101
+ }
102
+ };
103
+ function exprParams(parts, columns, label) {
104
+ return new ParameterizedSQLExpression(parts, columns, label);
105
+ }
106
+ function expr(sql2, columns, label) {
107
+ return new SQLExpression(sql2, columns, label);
108
+ }
109
+ function desc(e) {
110
+ return Object.assign(
111
+ expr(`${asColumn(e)} DESC NULLS LAST`, e?.columns, e?.label),
112
+ { desc: true }
113
+ );
114
+ }
115
+ function transform(func2, label) {
116
+ return (value) => expr(func2(value), asColumn(value).columns, label);
117
+ }
118
+
119
+ // src/sql-tag.js
120
+ function sql(strings, ...exprs) {
121
+ const spans = [strings[0]];
122
+ const colset = /* @__PURE__ */ new Set();
123
+ const n = exprs.length;
124
+ for (let i = 0, k = 0; i < n; ) {
125
+ const e = exprs[i];
126
+ if (isParamLike(e)) {
127
+ spans[++k] = e;
128
+ } else {
129
+ if (Array.isArray(e.columns)) {
130
+ e.columns.forEach((col) => colset.add(col));
131
+ }
132
+ spans[k] += String(e);
133
+ }
134
+ const s = strings[++i];
135
+ if (isParamLike(spans[k])) {
136
+ spans[++k] = s;
137
+ } else {
138
+ spans[k] += s;
139
+ }
140
+ }
141
+ const columns = Array.from(colset);
142
+ return spans.length > 1 ? exprParams(spans, columns) : expr(spans[0], columns);
143
+ }
144
+
92
145
  // src/literal.js
93
146
  var Literal = class {
94
147
  constructor(value) {
@@ -431,7 +484,7 @@ var Query = class {
431
484
  list.push({ as: e, from: asRelation(e) });
432
485
  } else if (e instanceof Ref) {
433
486
  list.push({ as: e.table, from: e });
434
- } else if (isQuery(e) || Object.hasOwn(e, "toString")) {
487
+ } else if (isQuery(e) || isExpression(e)) {
435
488
  list.push({ from: e });
436
489
  } else if (Array.isArray(e)) {
437
490
  list.push({ as: unquote(e[0]), from: asRelation(e[1]) });
@@ -588,60 +641,60 @@ var Query = class {
588
641
  offset,
589
642
  with: cte
590
643
  } = this.query;
591
- const sql = [];
644
+ const sql2 = [];
592
645
  if (cte.length) {
593
646
  const list = cte.map(({ as, query }) => `"${as}" AS (${query})`);
594
- sql.push(`WITH ${list.join(", ")}`);
647
+ sql2.push(`WITH ${list.join(", ")}`);
595
648
  }
596
649
  const sels = select.map(
597
650
  ({ as, expr: expr2 }) => isColumnRefFor(expr2, as) && !expr2.table ? `${expr2}` : `${expr2} AS "${as}"`
598
651
  );
599
- sql.push(`SELECT${distinct ? " DISTINCT" : ""} ${sels.join(", ")}`);
652
+ sql2.push(`SELECT${distinct ? " DISTINCT" : ""} ${sels.join(", ")}`);
600
653
  if (from.length) {
601
654
  const rels = from.map(({ as, from: from2 }) => {
602
655
  const rel = isQuery(from2) ? `(${from2})` : `${from2}`;
603
656
  return !as || as === from2.table ? rel : `${rel} AS "${as}"`;
604
657
  });
605
- sql.push(`FROM ${rels.join(", ")}`);
658
+ sql2.push(`FROM ${rels.join(", ")}`);
606
659
  }
607
660
  if (sample) {
608
661
  const { rows, perc, method, seed } = sample;
609
662
  const size = rows ? `${rows} ROWS` : `${perc} PERCENT`;
610
663
  const how = method ? ` (${method}${seed != null ? `, ${seed}` : ""})` : "";
611
- sql.push(`USING SAMPLE ${size}${how}`);
664
+ sql2.push(`USING SAMPLE ${size}${how}`);
612
665
  }
613
666
  if (where.length) {
614
667
  const clauses = where.map(String).filter((x) => x).join(" AND ");
615
668
  if (clauses)
616
- sql.push(`WHERE ${clauses}`);
669
+ sql2.push(`WHERE ${clauses}`);
617
670
  }
618
671
  if (groupby.length) {
619
- sql.push(`GROUP BY ${groupby.join(", ")}`);
672
+ sql2.push(`GROUP BY ${groupby.join(", ")}`);
620
673
  }
621
674
  if (having.length) {
622
675
  const clauses = having.map(String).filter((x) => x).join(" AND ");
623
676
  if (clauses)
624
- sql.push(`HAVING ${clauses}`);
677
+ sql2.push(`HAVING ${clauses}`);
625
678
  }
626
679
  if (window.length) {
627
680
  const windows = window.map(({ as, expr: expr2 }) => `"${as}" AS (${expr2})`);
628
- sql.push(`WINDOW ${windows.join(", ")}`);
681
+ sql2.push(`WINDOW ${windows.join(", ")}`);
629
682
  }
630
683
  if (qualify.length) {
631
684
  const clauses = qualify.map(String).filter((x) => x).join(" AND ");
632
685
  if (clauses)
633
- sql.push(`QUALIFY ${clauses}`);
686
+ sql2.push(`QUALIFY ${clauses}`);
634
687
  }
635
688
  if (orderby.length) {
636
- sql.push(`ORDER BY ${orderby.join(", ")}`);
689
+ sql2.push(`ORDER BY ${orderby.join(", ")}`);
637
690
  }
638
691
  if (Number.isFinite(limit)) {
639
- sql.push(`LIMIT ${limit}`);
692
+ sql2.push(`LIMIT ${limit}`);
640
693
  }
641
694
  if (Number.isFinite(offset)) {
642
- sql.push(`OFFSET ${offset}`);
695
+ sql2.push(`OFFSET ${offset}`);
643
696
  }
644
- return sql.join(" ");
697
+ return sql2.join(" ");
645
698
  }
646
699
  };
647
700
  var SetOperation = class {
@@ -692,17 +745,17 @@ var SetOperation = class {
692
745
  }
693
746
  toString() {
694
747
  const { op, queries, query: { orderby, limit, offset } } = this;
695
- const sql = [queries.join(` ${op} `)];
748
+ const sql2 = [queries.join(` ${op} `)];
696
749
  if (orderby.length) {
697
- sql.push(`ORDER BY ${orderby.join(", ")}`);
750
+ sql2.push(`ORDER BY ${orderby.join(", ")}`);
698
751
  }
699
752
  if (Number.isFinite(limit)) {
700
- sql.push(`LIMIT ${limit}`);
753
+ sql2.push(`LIMIT ${limit}`);
701
754
  }
702
755
  if (Number.isFinite(offset)) {
703
- sql.push(`OFFSET ${offset}`);
756
+ sql2.push(`OFFSET ${offset}`);
704
757
  }
705
- return sql.join(" ");
758
+ return sql2.join(" ");
706
759
  }
707
760
  };
708
761
  function isQuery(value) {
@@ -735,6 +788,7 @@ export {
735
788
  epoch_ms,
736
789
  eq,
737
790
  expr,
791
+ exprParams,
738
792
  first,
739
793
  gt,
740
794
  gte,
@@ -780,6 +834,7 @@ export {
780
834
  regrSlope,
781
835
  relation,
782
836
  skewness,
837
+ sql,
783
838
  stddev,
784
839
  stddevPop,
785
840
  stringAgg,
@@ -1 +1 @@
1
- var m=class{constructor(t,r){t&&(this.table=String(t)),r&&(this.column=r)}get columns(){return this.column?[this.column]:[]}toString(){let{table:t,column:r}=this;if(r){let n=r==="*"?r:`"${r}"`;return(t?`"${t}".`:"")+n}else return`"${t}"`}};function _(e,t){return e instanceof m&&e.column===t}function f(e){return typeof e=="string"?B(e):e}function E(e){return typeof e=="string"?X(e):e}function X(e){return new m(e)}function B(e,t){return arguments.length===1?new m(null,e):new m(e,t)}function k(e){return new m(e,"*")}function K(e){return e=f(e),{expr:e,desc:!0,toString:()=>`${e} DESC NULLS LAST`,get columns(){return e.columns?.()||[]}}}function V(e,t,r){return{expr:e,label:r,toString:()=>`${e}`,get columns(){return t||[]}}}function b(e,t){return r=>V(e(r),f(r).columns,t)}function h(e){return typeof e=="string"?`"${e}"`:d(e)}function d(e){switch(typeof e){case"boolean":return e?"TRUE":"FALSE";case"string":return`'${e}'`;default:return e==null?"NULL":e instanceof Date?`MAKE_DATE(${e.getUTCFullYear()}, ${e.getUTCMonth()+1}, ${e.getUTCDate()})`:e instanceof RegExp?`'${e.source}'`:String(e)}}var I=class{constructor(t){this.value=t}toString(){return d(this.value)}},z=e=>new I(e);function q(...e){return e.flat().flatMap(t=>t?.columns||[])}var D=class{constructor(t,r){this.op=t,this.a=f(r)}get columns(){return q(this.a)}visit(t){t(this.op,this)}toString(){let{op:t,a:r}=this;return`(${h(r)} ${t})`}};function C(e){return t=>new D(e,t)}var J=C("NOT"),Z=C("IS NULL"),tt=C("IS NOT NULL"),O=class{constructor(t,r,n){this.op=t,this.a=f(r),this.b=f(n)}get columns(){return q(this.a,this.b)}visit(t){t(this.op,this)}toString(){let{op:t,a:r,b:n}=this;return`(${h(r)} ${t} ${h(n)})`}};function w(e){return(t,r)=>new O(e,t,r)}var rt=w("="),et=w("<>"),nt=w("<"),st=w(">"),ot=w("<="),it=w(">="),ct=w("IS DISTINCT FROM"),ut=w("IS NOT DISTINCT FROM"),L=class{constructor(t,r,n){this.op=t,this.expr=f(r),this.value=n?.map(f)}get columns(){return q(this.expr,this.value)}visit(t){t(this.op,this)}toString(){let{op:t,expr:r,value:n}=this;if(!n)return"";let[s,i]=n;return`(${h(r)} ${t} ${h(s)} AND ${h(i)})`}};function v(e){return(t,r)=>new L(e,t,r)}var lt=v("BETWEEN"),ft=v("NOT BETWEEN"),R=class{constructor(t,r){this.op=t,this.value=r.map(f)}get columns(){return q(this.value)}visit(t){t(this.op,this),this.value?.forEach(r=>r.visit(t))}toString(){let{op:t,value:r}=this;return!r||r.length===0?"":r.length===1?h(r[0]):`(${r.map(h).filter(n=>n).join(` ${t} `)})`}};function ht(...e){return new R("AND",e.flat())}function pt(...e){return new R("OR",e.flat())}var F=class{constructor(t,r){this.func=t,this.args=(r||[]).map(f)}get column(){return this.columns[0]}get columns(){return this.args.flatMap(t=>t.columns||[])}toString(){let{func:t,args:r}=this;return`${t}(${r.map(h).join(", ")})`}};function x(e){return(...t)=>new F(e,t)}var at=x("regexp_matches"),gt=x("contains"),mt=x("prefix"),xt=x("suffix"),$t=x("lower"),St=x("upper"),yt=x("length"),wt=x("isnan"),Nt=x("isfinite"),Et=x("isinf");var G=class{constructor(t,r){this.aggregate=t,this.args=(r||[]).map(f)}get label(){return this.aggregate.toLowerCase()+(this.args.length?` ${this.columns.join(", ")}`:"")}get column(){return this.columns[0]}get columns(){return this.args.flatMap(t=>t.columns||[])}distinct(){return this.isDistinct=!0,this}where(t){return this.filter=t,this}toString(){let{aggregate:t,args:r,isDistinct:n,filter:s}=this,i=r.length===0?"*":r.map(h).join(", "),c=n?"DISTINCT ":"",p=s?` FILTER (WHERE ${h(s)})`:"",y=t==="COUNT"?"::INTEGER":"";return p&&y?`(${t}(${c}${i})${p})${y}`:`${t}(${c}${i})${p}${y}`}};function o(e){return(...t)=>new G(e,t)}var dt=o("COUNT"),Rt=o("AVG"),qt=o("AVG"),At=o("MAD"),Tt=o("MAX"),bt=o("MIN"),It=o("SUM"),Dt=o("PRODUCT"),Ot=o("MEDIAN"),Lt=o("QUANTILE"),Ct=o("MODE"),Ft=o("VARIANCE"),Gt=o("STDDEV"),Ut=o("SKEWNESS"),Mt=o("KURTOSIS"),Pt=o("ENTROPY"),jt=o("VAR_POP"),Yt=o("STDDEV_POP"),_t=o("CORR"),Xt=o("COVAR_POP"),Bt=o("REGR_INTERCEPT"),Vt=o("REGR_SLOPE"),vt=o("REGR_COUNT"),Qt=o("REGR_R2"),Wt=o("REGR_SYY"),Ht=o("REGR_SXX"),kt=o("REGR_SXY"),Kt=o("REGR_AVGX"),zt=o("REGR_AVGY"),Jt=o("FIRST"),Zt=o("LAST"),tr=o("ARG_MIN"),rr=o("ARG_MAX"),er=o("STRING_AGG"),nr=o("ARRAY_AGG");var sr=b(e=>`(1000 * (epoch(${e}) - second(${e})) + millisecond(${e}))::DOUBLE`);function or(e){return{op:"UNNEST",arg:e,toString(){return`UNNEST(${Array.isArray(e)?`[${e.join(", ")}]`:e})`}}}var $=class{static select(...t){return new $().select(...t)}static from(...t){return new $().from(...t)}static with(...t){return new $().with(...t)}static union(...t){return new S("UNION",t.flat())}static unionAll(...t){return new S("UNION ALL",t.flat())}static intersect(...t){return new S("INTERSECT",t.flat())}static except(...t){return new S("EXCEPT",t.flat())}constructor(){this.query={with:[],select:[],from:[],where:[],groupby:[],having:[],window:[],qualify:[],orderby:[]}}clone(){let t=new $;return t.query={...this.query},t}with(...t){let{query:r}=this;if(t.length===0)return r.with;{let n=[],s=(i,c)=>{let p=c.clone();p.cteFor=this,n.push({as:i,query:p})};return t.flat().forEach(i=>{if(i!=null)if(i.as&&i.query)s(i.as,i.query);else for(let c in i)s(c,i[c])}),r.with=r.with.concat(n),this}}select(...t){let{query:r}=this;if(t.length===0)return r.select;{let n=[];return t.flat().forEach(s=>{if(s!=null)if(typeof s=="string")n.push({as:s,expr:f(s)});else if(s instanceof m)n.push({as:s.column,expr:s});else if(Array.isArray(s))n.push({as:s[0],expr:s[1]});else for(let i in s)n.push({as:A(i),expr:f(s[i])})}),r.select=r.select.concat(n),this}}$select(...t){return this.query.select=[],this.select(...t)}distinct(t=!0){return this.query.distinct=!!t,this}from(...t){let{query:r}=this;if(t.length===0)return r.from;{let n=[];return t.flat().forEach(s=>{if(s!=null)if(typeof s=="string")n.push({as:s,from:E(s)});else if(s instanceof m)n.push({as:s.table,from:s});else if(T(s)||Object.hasOwn(s,"toString"))n.push({from:s});else if(Array.isArray(s))n.push({as:A(s[0]),from:E(s[1])});else for(let i in s)n.push({as:A(i),from:E(s[i])})}),r.from=r.from.concat(n),this}}$from(...t){return this.query.from=[],this.from(...t)}sample(t){let{query:r}=this;if(arguments.length===0)return r.sample;{let n=t;return typeof t=="number"&&(n=t>0&&t<1?{perc:100*t}:{rows:Math.round(t)}),r.sample=n,this}}where(...t){let{query:r}=this;return t.length===0?r.where:(r.where=r.where.concat(t.flat().filter(n=>n)),this)}$where(...t){return this.query.where=[],this.where(...t)}groupby(...t){let{query:r}=this;return t.length===0?r.groupby:(r.groupby=r.groupby.concat(t.flat().filter(n=>n).map(f)),this)}having(...t){let{query:r}=this;return t.length===0?r.having:(r.having=r.having.concat(t.flat().filter(n=>n)),this)}window(...t){let{query:r}=this;if(t.length===0)return r.window;{let n=[];return t.flat().forEach(s=>{if(s!=null)for(let i in s)n.push({as:A(i),expr:s[i]})}),r.window=r.window.concat(n),this}}qualify(...t){let{query:r}=this;return t.length===0?r.qualify:(r.qualify=r.qualify.concat(t.flat().filter(n=>n)),this)}orderby(...t){let{query:r}=this;return t.length===0?r.orderby:(r.orderby=r.orderby.concat(t.flat().filter(n=>n).map(f)),this)}limit(t){let{query:r}=this;return arguments.length===0?r.limit:(r.limit=Number.isFinite(t)?t:void 0,this)}offset(t){let{query:r}=this;return arguments.length===0?r.offset:(r.offset=Number.isFinite(t)?t:void 0,this)}get subqueries(){let{query:t,cteFor:r}=this,s=(r?.query||t).with?.reduce((c,{as:p,query:y})=>(c[p]=y,c),{}),i=[];return t.from.forEach(({from:c})=>{if(T(c))i.push(c);else if(s[c.table]){let p=s[c.table];i.push(p)}}),i}toString(){let{select:t,distinct:r,from:n,sample:s,where:i,groupby:c,having:p,window:y,qualify:U,orderby:M,limit:P,offset:j,with:Y}=this.query,a=[];if(Y.length){let u=Y.map(({as:l,query:g})=>`"${l}" AS (${g})`);a.push(`WITH ${u.join(", ")}`)}let Q=t.map(({as:u,expr:l})=>_(l,u)&&!l.table?`${l}`:`${l} AS "${u}"`);if(a.push(`SELECT${r?" DISTINCT":""} ${Q.join(", ")}`),n.length){let u=n.map(({as:l,from:g})=>{let N=T(g)?`(${g})`:`${g}`;return!l||l===g.table?N:`${N} AS "${l}"`});a.push(`FROM ${u.join(", ")}`)}if(s){let{rows:u,perc:l,method:g,seed:N}=s,W=u?`${u} ROWS`:`${l} PERCENT`,H=g?` (${g}${N!=null?`, ${N}`:""})`:"";a.push(`USING SAMPLE ${W}${H}`)}if(i.length){let u=i.map(String).filter(l=>l).join(" AND ");u&&a.push(`WHERE ${u}`)}if(c.length&&a.push(`GROUP BY ${c.join(", ")}`),p.length){let u=p.map(String).filter(l=>l).join(" AND ");u&&a.push(`HAVING ${u}`)}if(y.length){let u=y.map(({as:l,expr:g})=>`"${l}" AS (${g})`);a.push(`WINDOW ${u.join(", ")}`)}if(U.length){let u=U.map(String).filter(l=>l).join(" AND ");u&&a.push(`QUALIFY ${u}`)}return M.length&&a.push(`ORDER BY ${M.join(", ")}`),Number.isFinite(P)&&a.push(`LIMIT ${P}`),Number.isFinite(j)&&a.push(`OFFSET ${j}`),a.join(" ")}},S=class{constructor(t,r){this.op=t,this.queries=r.map(n=>n.clone()),this.query={orderby:[]}}clone(){let t=new S(this.op,this.queries);return t.query={...this.query},t}orderby(...t){let{query:r}=this;return t.length===0?r.orderby:(r.orderby=r.orderby.concat(t.flat().filter(n=>n).map(f)),this)}limit(t){let{query:r}=this;return arguments.length===0?r.limit:(r.limit=Number.isFinite(t)?t:void 0,this)}offset(t){let{query:r}=this;return arguments.length===0?r.offset:(r.offset=Number.isFinite(t)?t:void 0,this)}get subqueries(){let{queries:t,cteFor:r}=this;return r&&t.forEach(n=>n.cteFor=r),t}toString(){let{op:t,queries:r,query:{orderby:n,limit:s,offset:i}}=this,c=[r.join(` ${t} `)];return n.length&&c.push(`ORDER BY ${n.join(", ")}`),Number.isFinite(s)&&c.push(`LIMIT ${s}`),Number.isFinite(i)&&c.push(`OFFSET ${i}`),c.join(" ")}};function T(e){return e instanceof $||e instanceof S}function A(e){return ir(e)?e.slice(1,-1):e}function ir(e){return e[0]==='"'&&e[e.length-1]==='"'}export{$ as Query,m as Ref,k as all,ht as and,rr as argmax,tr as argmin,nr as arrayAgg,f as asColumn,E as asRelation,Rt as avg,B as column,gt as contains,_t as corr,dt as count,Xt as covarPop,K as desc,Pt as entropy,sr as epoch_ms,rt as eq,V as expr,Jt as first,st as gt,it as gte,lt as isBetween,ct as isDistinct,Nt as isFinite,Et as isInfinite,wt as isNaN,ft as isNotBetween,ut as isNotDistinct,tt as isNotNull,Z as isNull,T as isQuery,Mt as kurtosis,Zt as last,yt as length,z as literal,d as literalToSQL,$t as lower,nt as lt,ot as lte,At as mad,Tt as max,qt as mean,Ot as median,bt as min,Ct as mode,et as neq,J as not,pt as or,mt as prefix,Dt as product,Lt as quantile,at as regexp_matches,Kt as regrAvgX,zt as regrAvgY,vt as regrCount,Bt as regrIntercept,Qt as regrR2,Ht as regrSXX,kt as regrSXY,Wt as regrSYY,Vt as regrSlope,X as relation,Ut as skewness,Gt as stddev,Yt as stddevPop,er as stringAgg,xt as suffix,It as sum,h as toSQL,b as transform,or as unnest,St as upper,jt as varPop,Ft as variance};
1
+ var x=class{constructor(t,r){t&&(this.table=String(t)),r&&(this.column=r)}get columns(){return this.column?[this.column]:[]}toString(){let{table:t,column:r}=this;if(r){let n=r==="*"?r:`"${r}"`;return(t?`"${t}".`:"")+n}else return`"${t}"`}};function W(e,t){return e instanceof x&&e.column===t}function u(e){return typeof e=="string"?k(e):e}function R(e){return typeof e=="string"?Q(e):e}function Q(e){return new x(e)}function k(e,t){return arguments.length===1?new x(null,e):new x(e,t)}function tt(e){return new x(e,"*")}function f(e){return typeof e=="string"?`"${e}"`:y(e)}function y(e){switch(typeof e){case"boolean":return e?"TRUE":"FALSE";case"string":return`'${e}'`;default:return e==null?"NULL":e instanceof Date?`MAKE_DATE(${e.getUTCFullYear()}, ${e.getUTCMonth()+1}, ${e.getUTCDate()})`:e instanceof RegExp?`'${e.source}'`:String(e)}}var q=e=>typeof e?.addEventListener=="function";function H(e){return e instanceof A}var A=class{constructor(t,r,n){this.expr=t,this.label=n,this.columns=r||[]}toString(){return`${this.expr}`}},C=class extends A{constructor(t,r,n){let s=new Set;for(let o of t)q(o)&&s.add(o);s.forEach(o=>{o.addEventListener("value",()=>this.update())}),super(t,r,n)}toString(){return this.expr.map(t=>q(t)?y(t.value):t).join("")}addEventListener(t,r){let n=this.map||(this.map=new Map);(n.get(t)||(n.set(t,new Set),n.get(t))).add(r)}update(){this.map?.get("value")?.forEach(t=>t(this))}};function F(e,t,r){return new C(e,t,r)}function T(e,t,r){return new A(e,t,r)}function rt(e){return Object.assign(T(`${u(e)} DESC NULLS LAST`,e?.columns,e?.label),{desc:!0})}function G(e,t){return r=>T(e(r),u(r).columns,t)}function et(e,...t){let r=[e[0]],n=new Set,s=t.length;for(let c=0,p=0;c<s;){let h=t[c];q(h)?r[++p]=h:(Array.isArray(h.columns)&&h.columns.forEach(b=>n.add(b)),r[p]+=String(h));let E=e[++c];q(r[p])?r[++p]=E:r[p]+=E}let o=Array.from(n);return r.length>1?F(r,o):T(r[0],o)}var P=class{constructor(t){this.value=t}toString(){return y(this.value)}},nt=e=>new P(e);function L(...e){return e.flat().flatMap(t=>t?.columns||[])}var U=class{constructor(t,r){this.op=t,this.a=u(r)}get columns(){return L(this.a)}visit(t){t(this.op,this)}toString(){let{op:t,a:r}=this;return`(${f(r)} ${t})`}};function Y(e){return t=>new U(e,t)}var st=Y("NOT"),ot=Y("IS NULL"),it=Y("IS NOT NULL"),M=class{constructor(t,r,n){this.op=t,this.a=u(r),this.b=u(n)}get columns(){return L(this.a,this.b)}visit(t){t(this.op,this)}toString(){let{op:t,a:r,b:n}=this;return`(${f(r)} ${t} ${f(n)})`}};function w(e){return(t,r)=>new M(e,t,r)}var ct=w("="),ut=w("<>"),lt=w("<"),at=w(">"),pt=w("<="),ft=w(">="),ht=w("IS DISTINCT FROM"),mt=w("IS NOT DISTINCT FROM"),j=class{constructor(t,r,n){this.op=t,this.expr=u(r),this.value=n?.map(u)}get columns(){return L(this.expr,this.value)}visit(t){t(this.op,this)}toString(){let{op:t,expr:r,value:n}=this;if(!n)return"";let[s,o]=n;return`(${f(r)} ${t} ${f(s)} AND ${f(o)})`}};function K(e){return(t,r)=>new j(e,t,r)}var gt=K("BETWEEN"),xt=K("NOT BETWEEN"),I=class{constructor(t,r){this.op=t,this.value=r.map(u)}get columns(){return L(this.value)}visit(t){t(this.op,this),this.value?.forEach(r=>r.visit(t))}toString(){let{op:t,value:r}=this;return!r||r.length===0?"":r.length===1?f(r[0]):`(${r.map(f).filter(n=>n).join(` ${t} `)})`}};function $t(...e){return new I("AND",e.flat())}function St(...e){return new I("OR",e.flat())}var _=class{constructor(t,r){this.func=t,this.args=(r||[]).map(u)}get column(){return this.columns[0]}get columns(){return this.args.flatMap(t=>t.columns||[])}toString(){let{func:t,args:r}=this;return`${t}(${r.map(f).join(", ")})`}};function $(e){return(...t)=>new _(e,t)}var dt=$("regexp_matches"),wt=$("contains"),yt=$("prefix"),Et=$("suffix"),Nt=$("lower"),Rt=$("upper"),qt=$("length"),At=$("isnan"),Tt=$("isfinite"),bt=$("isinf");var v=class{constructor(t,r){this.aggregate=t,this.args=(r||[]).map(u)}get label(){return this.aggregate.toLowerCase()+(this.args.length?` ${this.columns.join(", ")}`:"")}get column(){return this.columns[0]}get columns(){return this.args.flatMap(t=>t.columns||[])}distinct(){return this.isDistinct=!0,this}where(t){return this.filter=t,this}toString(){let{aggregate:t,args:r,isDistinct:n,filter:s}=this,o=r.length===0?"*":r.map(f).join(", "),c=n?"DISTINCT ":"",p=s?` FILTER (WHERE ${f(s)})`:"",h=t==="COUNT"?"::INTEGER":"";return p&&h?`(${t}(${c}${o})${p})${h}`:`${t}(${c}${o})${p}${h}`}};function i(e){return(...t)=>new v(e,t)}var It=i("COUNT"),Lt=i("AVG"),Dt=i("AVG"),Ot=i("MAD"),Ct=i("MAX"),Ft=i("MIN"),Gt=i("SUM"),Pt=i("PRODUCT"),Ut=i("MEDIAN"),Mt=i("QUANTILE"),jt=i("MODE"),Yt=i("VARIANCE"),_t=i("STDDEV"),vt=i("SKEWNESS"),Xt=i("KURTOSIS"),Bt=i("ENTROPY"),Vt=i("VAR_POP"),Wt=i("STDDEV_POP"),Qt=i("CORR"),kt=i("COVAR_POP"),Ht=i("REGR_INTERCEPT"),Kt=i("REGR_SLOPE"),zt=i("REGR_COUNT"),Jt=i("REGR_R2"),Zt=i("REGR_SYY"),tr=i("REGR_SXX"),rr=i("REGR_SXY"),er=i("REGR_AVGX"),nr=i("REGR_AVGY"),sr=i("FIRST"),or=i("LAST"),ir=i("ARG_MIN"),cr=i("ARG_MAX"),ur=i("STRING_AGG"),lr=i("ARRAY_AGG");var ar=G(e=>`(1000 * (epoch(${e}) - second(${e})) + millisecond(${e}))::DOUBLE`);function pr(e){return{op:"UNNEST",arg:e,toString(){return`UNNEST(${Array.isArray(e)?`[${e.join(", ")}]`:e})`}}}var S=class{static select(...t){return new S().select(...t)}static from(...t){return new S().from(...t)}static with(...t){return new S().with(...t)}static union(...t){return new d("UNION",t.flat())}static unionAll(...t){return new d("UNION ALL",t.flat())}static intersect(...t){return new d("INTERSECT",t.flat())}static except(...t){return new d("EXCEPT",t.flat())}constructor(){this.query={with:[],select:[],from:[],where:[],groupby:[],having:[],window:[],qualify:[],orderby:[]}}clone(){let t=new S;return t.query={...this.query},t}with(...t){let{query:r}=this;if(t.length===0)return r.with;{let n=[],s=(o,c)=>{let p=c.clone();p.cteFor=this,n.push({as:o,query:p})};return t.flat().forEach(o=>{if(o!=null)if(o.as&&o.query)s(o.as,o.query);else for(let c in o)s(c,o[c])}),r.with=r.with.concat(n),this}}select(...t){let{query:r}=this;if(t.length===0)return r.select;{let n=[];return t.flat().forEach(s=>{if(s!=null)if(typeof s=="string")n.push({as:s,expr:u(s)});else if(s instanceof x)n.push({as:s.column,expr:s});else if(Array.isArray(s))n.push({as:s[0],expr:s[1]});else for(let o in s)n.push({as:D(o),expr:u(s[o])})}),r.select=r.select.concat(n),this}}$select(...t){return this.query.select=[],this.select(...t)}distinct(t=!0){return this.query.distinct=!!t,this}from(...t){let{query:r}=this;if(t.length===0)return r.from;{let n=[];return t.flat().forEach(s=>{if(s!=null)if(typeof s=="string")n.push({as:s,from:R(s)});else if(s instanceof x)n.push({as:s.table,from:s});else if(O(s)||H(s))n.push({from:s});else if(Array.isArray(s))n.push({as:D(s[0]),from:R(s[1])});else for(let o in s)n.push({as:D(o),from:R(s[o])})}),r.from=r.from.concat(n),this}}$from(...t){return this.query.from=[],this.from(...t)}sample(t){let{query:r}=this;if(arguments.length===0)return r.sample;{let n=t;return typeof t=="number"&&(n=t>0&&t<1?{perc:100*t}:{rows:Math.round(t)}),r.sample=n,this}}where(...t){let{query:r}=this;return t.length===0?r.where:(r.where=r.where.concat(t.flat().filter(n=>n)),this)}$where(...t){return this.query.where=[],this.where(...t)}groupby(...t){let{query:r}=this;return t.length===0?r.groupby:(r.groupby=r.groupby.concat(t.flat().filter(n=>n).map(u)),this)}having(...t){let{query:r}=this;return t.length===0?r.having:(r.having=r.having.concat(t.flat().filter(n=>n)),this)}window(...t){let{query:r}=this;if(t.length===0)return r.window;{let n=[];return t.flat().forEach(s=>{if(s!=null)for(let o in s)n.push({as:D(o),expr:s[o]})}),r.window=r.window.concat(n),this}}qualify(...t){let{query:r}=this;return t.length===0?r.qualify:(r.qualify=r.qualify.concat(t.flat().filter(n=>n)),this)}orderby(...t){let{query:r}=this;return t.length===0?r.orderby:(r.orderby=r.orderby.concat(t.flat().filter(n=>n).map(u)),this)}limit(t){let{query:r}=this;return arguments.length===0?r.limit:(r.limit=Number.isFinite(t)?t:void 0,this)}offset(t){let{query:r}=this;return arguments.length===0?r.offset:(r.offset=Number.isFinite(t)?t:void 0,this)}get subqueries(){let{query:t,cteFor:r}=this,s=(r?.query||t).with?.reduce((c,{as:p,query:h})=>(c[p]=h,c),{}),o=[];return t.from.forEach(({from:c})=>{if(O(c))o.push(c);else if(s[c.table]){let p=s[c.table];o.push(p)}}),o}toString(){let{select:t,distinct:r,from:n,sample:s,where:o,groupby:c,having:p,window:h,qualify:E,orderby:b,limit:X,offset:B,with:V}=this.query,m=[];if(V.length){let l=V.map(({as:a,query:g})=>`"${a}" AS (${g})`);m.push(`WITH ${l.join(", ")}`)}let z=t.map(({as:l,expr:a})=>W(a,l)&&!a.table?`${a}`:`${a} AS "${l}"`);if(m.push(`SELECT${r?" DISTINCT":""} ${z.join(", ")}`),n.length){let l=n.map(({as:a,from:g})=>{let N=O(g)?`(${g})`:`${g}`;return!a||a===g.table?N:`${N} AS "${a}"`});m.push(`FROM ${l.join(", ")}`)}if(s){let{rows:l,perc:a,method:g,seed:N}=s,J=l?`${l} ROWS`:`${a} PERCENT`,Z=g?` (${g}${N!=null?`, ${N}`:""})`:"";m.push(`USING SAMPLE ${J}${Z}`)}if(o.length){let l=o.map(String).filter(a=>a).join(" AND ");l&&m.push(`WHERE ${l}`)}if(c.length&&m.push(`GROUP BY ${c.join(", ")}`),p.length){let l=p.map(String).filter(a=>a).join(" AND ");l&&m.push(`HAVING ${l}`)}if(h.length){let l=h.map(({as:a,expr:g})=>`"${a}" AS (${g})`);m.push(`WINDOW ${l.join(", ")}`)}if(E.length){let l=E.map(String).filter(a=>a).join(" AND ");l&&m.push(`QUALIFY ${l}`)}return b.length&&m.push(`ORDER BY ${b.join(", ")}`),Number.isFinite(X)&&m.push(`LIMIT ${X}`),Number.isFinite(B)&&m.push(`OFFSET ${B}`),m.join(" ")}},d=class{constructor(t,r){this.op=t,this.queries=r.map(n=>n.clone()),this.query={orderby:[]}}clone(){let t=new d(this.op,this.queries);return t.query={...this.query},t}orderby(...t){let{query:r}=this;return t.length===0?r.orderby:(r.orderby=r.orderby.concat(t.flat().filter(n=>n).map(u)),this)}limit(t){let{query:r}=this;return arguments.length===0?r.limit:(r.limit=Number.isFinite(t)?t:void 0,this)}offset(t){let{query:r}=this;return arguments.length===0?r.offset:(r.offset=Number.isFinite(t)?t:void 0,this)}get subqueries(){let{queries:t,cteFor:r}=this;return r&&t.forEach(n=>n.cteFor=r),t}toString(){let{op:t,queries:r,query:{orderby:n,limit:s,offset:o}}=this,c=[r.join(` ${t} `)];return n.length&&c.push(`ORDER BY ${n.join(", ")}`),Number.isFinite(s)&&c.push(`LIMIT ${s}`),Number.isFinite(o)&&c.push(`OFFSET ${o}`),c.join(" ")}};function O(e){return e instanceof S||e instanceof d}function D(e){return fr(e)?e.slice(1,-1):e}function fr(e){return e[0]==='"'&&e[e.length-1]==='"'}export{S as Query,x as Ref,tt as all,$t as and,cr as argmax,ir as argmin,lr as arrayAgg,u as asColumn,R as asRelation,Lt as avg,k as column,wt as contains,Qt as corr,It as count,kt as covarPop,rt as desc,Bt as entropy,ar as epoch_ms,ct as eq,T as expr,F as exprParams,sr as first,at as gt,ft as gte,gt as isBetween,ht as isDistinct,Tt as isFinite,bt as isInfinite,At as isNaN,xt as isNotBetween,mt as isNotDistinct,it as isNotNull,ot as isNull,O as isQuery,Xt as kurtosis,or as last,qt as length,nt as literal,y as literalToSQL,Nt as lower,lt,pt as lte,Ot as mad,Ct as max,Dt as mean,Ut as median,Ft as min,jt as mode,ut as neq,st as not,St as or,yt as prefix,Pt as product,Mt as quantile,dt as regexp_matches,er as regrAvgX,nr as regrAvgY,zt as regrCount,Ht as regrIntercept,Jt as regrR2,tr as regrSXX,rr as regrSXY,Zt as regrSYY,Kt as regrSlope,Q as relation,vt as skewness,et as sql,_t as stddev,Wt as stddevPop,ur as stringAgg,Et as suffix,Gt as sum,f as toSQL,G as transform,pr as unnest,Rt as upper,Vt as varPop,Yt as variance};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uwdata/mosaic-sql",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "SQL query construction and analysis.",
5
5
  "keywords": [
6
6
  "sql",
@@ -24,5 +24,6 @@
24
24
  "lint": "eslint src test --ext .js",
25
25
  "test": "mocha 'test/**/*-test.js'",
26
26
  "prepublishOnly": "npm run test && npm run lint && npm run build"
27
- }
27
+ },
28
+ "gitHead": "a7967c35349bdf7f00abb113ce1dd9abb233cd62"
28
29
  }
package/src/Query.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { isExpression } from './expression.js';
1
2
  import { asColumn, asRelation, isColumnRefFor, Ref } from './ref.js';
2
3
 
3
4
  export class Query {
@@ -126,7 +127,7 @@ export class Query {
126
127
  list.push({ as: e, from: asRelation(e) });
127
128
  } else if (e instanceof Ref) {
128
129
  list.push({ as: e.table, from: e });
129
- } else if (isQuery(e) || Object.hasOwn(e, 'toString')) {
130
+ } else if (isQuery(e) || isExpression(e)) {
130
131
  list.push({ from: e });
131
132
  } else if (Array.isArray(e)) {
132
133
  list.push({ as: unquote(e[0]), from: asRelation(e[1]) });
package/src/datetime.js CHANGED
@@ -1,4 +1,4 @@
1
- import { transform } from './ref.js';
1
+ import { transform } from './expression.js';
2
2
 
3
3
  export const epoch_ms = transform(
4
4
  d => `(1000 * (epoch(${d}) - second(${d})) + millisecond(${d}))::DOUBLE`
@@ -0,0 +1,68 @@
1
+ import { asColumn } from './ref.js';
2
+ import { literalToSQL } from './to-sql.js';
3
+
4
+ export const isParamLike = e => typeof e?.addEventListener === 'function';
5
+
6
+ export function isExpression(e) {
7
+ return e instanceof SQLExpression;
8
+ }
9
+
10
+ export class SQLExpression {
11
+ constructor(sql, columns, label) {
12
+ this.expr = sql;
13
+ this.label = label;
14
+ this.columns = columns || [];
15
+ }
16
+
17
+ toString() {
18
+ return `${this.expr}`;
19
+ }
20
+ }
21
+
22
+ export class ParameterizedSQLExpression extends SQLExpression{
23
+ constructor(parts, columns, label) {
24
+ const paramSet = new Set;
25
+ for (const part of parts) {
26
+ if (isParamLike(part)) paramSet.add(part);
27
+ }
28
+ paramSet.forEach(param => {
29
+ param.addEventListener('value', () => this.update());
30
+ });
31
+ super(parts, columns, label);
32
+ }
33
+
34
+ toString() {
35
+ return this.expr
36
+ .map(p => isParamLike(p) ? literalToSQL(p.value) : p)
37
+ .join('');
38
+ }
39
+
40
+ addEventListener(type, callback) {
41
+ const map = this.map || (this.map = new Map());
42
+ const set = map.get(type) || (map.set(type, new Set), map.get(type));
43
+ set.add(callback);
44
+ }
45
+
46
+ update() {
47
+ this.map?.get('value')?.forEach(callback => callback(this));
48
+ }
49
+ }
50
+
51
+ export function exprParams(parts, columns, label) {
52
+ return new ParameterizedSQLExpression(parts, columns, label);
53
+ }
54
+
55
+ export function expr(sql, columns, label) {
56
+ return new SQLExpression(sql, columns, label);
57
+ }
58
+
59
+ export function desc(e) {
60
+ return Object.assign(
61
+ expr(`${asColumn(e)} DESC NULLS LAST`, e?.columns, e?.label),
62
+ { desc: true }
63
+ );
64
+ }
65
+
66
+ export function transform(func, label) {
67
+ return value => expr(func(value), asColumn(value).columns, label);
68
+ }
package/src/index.js CHANGED
@@ -4,11 +4,19 @@ export {
4
4
  asRelation,
5
5
  all,
6
6
  column,
7
+ relation
8
+ } from './ref.js';
9
+
10
+ export {
11
+ transform,
7
12
  desc,
8
13
  expr,
9
- relation,
10
- transform
11
- } from './ref.js';
14
+ exprParams
15
+ } from './expression.js';
16
+
17
+ export {
18
+ sql
19
+ } from './sql-tag.js';
12
20
 
13
21
  export {
14
22
  toSQL,
package/src/ref.js CHANGED
@@ -44,30 +44,3 @@ export function column(table, column) {
44
44
  export function all(table) {
45
45
  return new Ref(table, '*');
46
46
  }
47
-
48
- export function desc(expr) {
49
- expr = asColumn(expr);
50
- return {
51
- expr,
52
- desc: true,
53
- toString: () => `${expr} DESC NULLS LAST`,
54
- get columns() { return expr.columns?.() || []; }
55
- };
56
- }
57
-
58
- export function expr(sql, columns, label) {
59
- return {
60
- expr: sql,
61
- label,
62
- toString: () => `${sql}`,
63
- get columns() { return columns || []; }
64
- };
65
- }
66
-
67
- export function transform(func, label) {
68
- return value => expr(
69
- func(value),
70
- asColumn(value).columns,
71
- label
72
- )
73
- }
package/src/sql-tag.js ADDED
@@ -0,0 +1,34 @@
1
+ import { expr, exprParams, isParamLike } from './expression.js';
2
+
3
+ /**
4
+ * Tag function for SQL expression strings. Interpolated values
5
+ * may be strings, other SQL expression objects (such as column
6
+ * references), or parameterized values.
7
+ */
8
+ export function sql(strings, ...exprs) {
9
+ const spans = [strings[0]];
10
+ const colset = new Set;
11
+ const n = exprs.length;
12
+ for (let i = 0, k = 0; i < n;) {
13
+ const e = exprs[i];
14
+ if (isParamLike(e)) {
15
+ spans[++k] = e;
16
+ } else {
17
+ if (Array.isArray(e.columns)) {
18
+ e.columns.forEach(col => colset.add(col));
19
+ }
20
+ spans[k] += String(e);
21
+ }
22
+ const s = strings[++i];
23
+ if (isParamLike(spans[k])) {
24
+ spans[++k] = s;
25
+ } else {
26
+ spans[k] += s;
27
+ }
28
+ }
29
+
30
+ const columns = Array.from(colset);
31
+ return spans.length > 1
32
+ ? exprParams(spans, columns)
33
+ : expr(spans[0], columns);
34
+ }