-
Notifications
You must be signed in to change notification settings - Fork 1
/
CSQL.pq
233 lines (208 loc) · 10.3 KB
/
CSQL.pq
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
section CSQL;
[DataSource.Kind="CSQL", Publish="CSQL.UI"]
shared CSQL.Tables = (server as text, database as text) as table =>
let
source = AdoDotNet.DataSource("System.Data.SqlClient", [server=server, database=database]),
tables = Value.NativeQuery(source, "select SCHEMA_NAME(schema_id) as [Schema], OBJECT_NAME(object_id) as [Table] from sys.tables order by [Schema], [Table]"),
withName = Table.AddColumn(tables, "Name", each [Schema] & "." & [Table]),
withData = Table.AddColumn(withName, "Data", each CSQL.Table(source, server, database, [Schema], [Table])),
asNavigation = Table.ToNavigationTable(withData, {"Schema", "Table"}, "Name", "Data")
in
asNavigation;
CSQL.Table = (source, server as text, database as text, schema as text, table as text) as table =>
let
tableName = QuoteIdentifier(schema) & "." & QuoteIdentifier(table),
columnNames = (params) => if params[columns]? = null then "*" else Text.Combine(List.Transform(params[columns], QuoteIdentifier), ","),
top = (params) => if params[count]? = null then " " else " top " & Text.From(params[count]) & " ",
predicates = (params) => if params[filter]? = null then "" else " where " & params[filter][Text],
query = (params) => "select" & top(params) & columnNames(params) & " from " & tableName & predicates(params),
execute = (params) => Value.NativeQuery(source, Diagnostics.Trace(1, query(params), query(params))),
tableType = Value.Type(execute([count=0])),
view = (params) => Table.View(null, [
GetType = () => if params[columns]? = null then tableType
else Value.Type(Table.SelectColumns(#table(tableType, {}), params[columns])),
GetRows = () => execute(params),
GetExpression = () => Invocation(Value.NativeQuery, {
Invocation(CSQL.Table, {Constant(server), Constant(database), Constant(schema), Constant(table)}),
Constant(query(params))
}),
OnSelectColumns = (columns) => @view(params & [columns = columns]),
OnTake = (count) => if count < Record.FieldOrDefault(params, "count", 1e12) then @view(params & [count=count]) else @view(params),
OnSelectRows = (condition) => if (params[count]? <> null) then ... else
@view(Record.AddField(params, "filter", AddFilter(tableType, condition, params[filter]?)))
])
in
view([]);
Constant = (value) => [Kind="Constant", Value=value];
Invocation = (fn as function, args as list) => [Kind="Invocation", Function=Constant(fn), Arguments=args];
QuoteIdentifier = (identifier) => "[" & Text.Replace(identifier, "]", "]]") & "]";
Table.ToNavigationTable = (
table as table,
keyColumns as list,
nameColumn as text,
dataColumn as text
) as table =>
let
tableType = Value.Type(table),
newTableType = Type.AddTableKey(tableType, keyColumns, true) meta
[
NavigationTable.NameColumn = nameColumn,
NavigationTable.DataColumn = dataColumn,
Preview.DelayColumn = dataColumn
],
navigationTable = Value.ReplaceType(table, newTableType)
in
navigationTable;
CSQL = [
TestConnection = (dataSourcePath) =>
let
json = Json.Document(dataSourcePath),
server = json[server],
database = json[database]
in
{ "CSQL.Tables", server, database },
Authentication = [
Windows = [],
UsernamePassword = []
],
Label = "ADO.NET Query Folding for SQL",
SupportsEncryption = false
];
Pattern.Value = (optional condition, optional selector) => (value) =>
if (condition = null or condition(value)) then
if (selector <> null) then selector(value)
else value
else null;
Pattern.Choice = (keySelector, options) => (value) => let
pattern = Record.Field(options, keySelector(value)),
appliedPattern = pattern(value)
in
appliedPattern;
Pattern.Record = (pattern, optional transform) => (value) => let
fieldNames = Record.FieldNames(pattern),
captured = List.Transform(fieldNames, each let
patternFieldValue = Record.Field(pattern, _),
valueFieldValue = Record.Field(value, _),
capturedFieldValue =
if (patternFieldValue is function) then patternFieldValue(valueFieldValue)
else if (patternFieldValue = valueFieldValue) then valueFieldValue
else ...
in
[Name = _, Value = capturedFieldValue]),
matchedRecord = Record.FromTable(Table.FromRecords(captured)),
transformedRecord = if (transform <> null) then transform(matchedRecord) else matchedRecord
in
transformedRecord;
AddFilter = (tableType, predicate, previous) =>
let
anything = Pattern.Value(),
return = (value) => Pattern.Value(null, each value),
typeOracle = (column) => Type.TableColumn(tableType, column),
sqlLogicalType = Value.Type(() => null), // sentinel type
makeLogical = (expr) => if expr[Type] = sqlLogicalType then expr
else if Type.Is(expr[Type], type nullable logical) then createBinaryExpr(operatorExpr("Equals"), expr, createConstantExpr(true))
else ...,
filterExpr = Pattern.Choice(
each [Kind],
[
Binary = binaryExpr,
FieldAccess = fieldAccessExpr,
Constant = constantExpr
]),
fieldAccessExpr = Pattern.Record(
[
Kind = "FieldAccess",
Expression = anything,
MemberName = anything
],
each if [Expression] <> RowExpression.Row then ... else
[
Type = typeOracle([MemberName]),
Text = QuoteIdentifier([MemberName]),
Precedence = -1
]),
constantExpr = Pattern.Record(
[
Kind = "Constant",
Value = anything
],
each createConstantExpr([Value])),
binaryExpr = Pattern.Record(
[
Kind = "Binary",
Operator = operatorExpr,
Left = filterExpr,
Right = filterExpr
],
each createBinaryExpr([Operator], [Left], [Right])),
operatorExpr = Pattern.Choice(
each _,
[
/* Not supported:
Multiply = return([Text = "*", Precedence = 2]),
Divide = return([Text = "/", Precedence = 2]),
Add = return([Text = "+", Precedence = 3]),
Subtract = return([Text = "-", Precedence = 3]),
Concatenate = return([Text = "+", Precedence = 3]),
*/
Equals = return([Text = "=", Precedence = 4, LogicalOutput = true]),
NotEquals = return([Text = "<>", Precedence = 4, LogicalOutput = true]),
GreaterThan = return([Text = ">", Precedence = 4, LogicalOutput = true]),
GreaterThanOrEquals = return([Text = ">=", Precedence = 4, LogicalOutput = true]),
LessThan = return([Text = "<", Precedence = 4, LogicalOutput = true]),
LessThanOrEquals = return([Text = "<=", Precedence = 4, LogicalOutput = true]),
Not = return([Text = "not", Precedence = 5, LogicalInput = true]),
And = return([Text = "and", Precedence = 6, LogicalInput = true]),
Or = return([Text = "or", Precedence = 7, LogicalInput = true])
]),
createConstantExpr = (value) =>
[
Type = Value.Type(value),
Text = escapeValue(value),
Precedence = -1
],
createBinaryExpr = (op, left, right) =>
let
logicalInput = (op[LogicalInput]? = true),
logicalOutput = (op[LogicalOutput]? = true),
l = if logicalInput then makeLogical(left) else left,
r = if logicalInput then makeLogical(right) else right,
leftText = if (comparePrecedence(op, l) < 0) then "(" & l[Text] & ")" else l[Text],
rightText = if (comparePrecedence(op, r) < 0) then "(" & r[Text] & ")" else r[Text]
in
if not logicalInput and (l[Type] = sqlLogicalType or r[Type] = sqlLogicalType) then ...
else if l[Type] = type null then
if r[Type] <> type null then @createBinaryExpr(op, r, l) else ...
else if op = "=" and r[Type] = type null then
[Type = sqlLogicalType, Text = "(" & l[Text] & ") is null"]
else if op = "<>" and r[Type] = type null then
[Type = sqlLogicalType, Text = "(" & l[Text] & ") is not null"]
else if (Type.NonNullable(l[Type]) = Type.NonNullable(r[Type]))
and (not Type.IsNullable(l[Type]) or not Type.IsNullable(r[Type])) then
[
Type = if logicalOutput then sqlLogicalType else Type.NonNullable(l[Type]),
Text = Text.Combine({leftText, " ", op[Text], " ", rightText}),
Precedence = precedence(op)
]
else ...,
precedence = (expressionOrOperator) =>
Record.FieldOrDefault(expressionOrOperator, "Precedence", 1000),
comparePrecedence = (x, y) =>
if (precedence(x) < precedence(y)) then -1
else if (precedence(x) > precedence(y)) then 1
else 0,
escapeValue = (value) =>
if (value = null) then "null"
else if (value is date) then "'" & Date.ToText(value, "yyyy-MM-dd") & "'"
else if (value is datetime) then "'" & DateTime.ToText(value, "yyyy-MM-dd HH:mm:ss") & "'"
else if (value is datetimezone) then "'" & DateTimeZone.ToText(value, "yyyy-MM-dd HH:mm:ss zzzz") & "'"
else if (value is text) then "N'" & Text.Replace(value, "'", "''") & "'"
else if (value is logical) then if value then "1" else "0"
else ...,
filter = filterExpr(RowExpression.From(predicate))
in
if previous <> null then createBinaryExpr(operatorExpr("And"), previous, filter) else makeLogical(filter);
CSQL.UI = [
Category = "Database",
ButtonText = { "ADO.NET Query Folding for SQL", "Query Folding via ADO.NET sample for SQL Server" }
];