Skip to content

Commit cde8e12

Browse files
committed
more extensive testing
1 parent a999e25 commit cde8e12

9 files changed

Lines changed: 194 additions & 48 deletions

File tree

src/QuerySQLite.jl

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
module QuerySQLite
22

3-
import Base: !, &, |, ==, !=, coalesce, collect, eltype, getproperty, length,
4-
in, isdone, isequal, isless, ismissing, iterate, IteratorSize, occursin, show,
5-
showerror, startswith
3+
import Base: !, &, |, ==, !=, *, +, %, abs, Char, coalesce, collect, eltype, getproperty, length,
4+
lowercase, in, isdone, isequal, isless, ismissing, iterate, IteratorSize, max, min,
5+
occursin, rand, show, showerror, startswith, string, uppercase
66
using Base: Generator, NamedTuple, RefValue, SizeUnknown, tail
77
using Base.Meta: quot
88
import Base.Multimedia: showable
@@ -36,4 +36,8 @@ include("show.jl")
3636
# 3b) Otherwise, define translate_call(::typeof(function), ...)
3737
# 4) If the SQL function uses special syntax, special case the SQLExpression show method
3838

39+
# There are two situtations in which Julia code cannot be translated into SQL
40+
# 1) Patterns based on non-functions or non-overloadable functions, e.g. if, &&. Use if_else and & instead
41+
# 2) Separate operations in SQL that are combined in Julia by dispatch, e.g. * for string concatention. Use `string` instead
42+
3943
end # module

src/code_instead.jl

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,32 @@ struct SourceCode{Source}
55
end
66

77
# Every time `SourceCode` objects are combined, check to see whether they all come from the same source
8-
function pop_sources!(sources, something)
8+
function pop_source!(sources, something)
99
something
1010
end
11-
function pop_sources!(sources, source_code::SourceCode)
11+
function pop_source!(sources, source_code::SourceCode)
1212
push!(sources, source_code.source)
1313
source_code.code
1414
end
15+
function key_pop_source!(sources, (key, source_code))
16+
code = pop_source!(souces, source_code)
17+
Expr(:kw, key, code)
18+
end
1519

16-
function combine_sources(a_function, source_codes...)
20+
function combine_sources(a_function, source_codes...; key_source_codes...)
1721
sources = Set(Any[])
18-
codes = partial_map(pop_sources!, sources, source_codes)
22+
codes = partial_map(pop_source!, sources, source_codes)
23+
key_codes = (
24+
key_pop_source!(sources, key_source_code)
25+
for key_source_code in key_source_codes
26+
)
1927
if length(sources) != 1
2028
error("Expected exactly one source; got ($(sources...))")
2129
else
22-
SourceCode(first(sources), Expr(:call, a_function, codes...))
30+
SourceCode(
31+
first(sources),
32+
Expr(:call, a_function, Expr(:parameters, key_codes...), codes...)
33+
)
2334
end
2435
end
2536

@@ -43,10 +54,12 @@ end
4354

4455
function code_instead(location, a_function, types...)
4556
arguments = ntuple(numbered_argument, length(types))
57+
keywords = Expr(:parameters, Expr(:..., :keywords))
4658
Expr(:function,
47-
Expr(:call, a_function, map_unrolled(assert_type, arguments, types)...),
59+
Expr(:call, a_function, keywords, map_unrolled(assert_type, arguments, types)...),
4860
Expr(:block, location, Expr(:call,
4961
combine_sources,
62+
keywords,
5063
a_function,
5164
map_unrolled(maybe_splat, arguments, types)...
5265
))
@@ -80,6 +93,24 @@ end
8093
@code_instead (|) Any SourceCode
8194
@code_instead (|) SourceCode SourceCode
8295

96+
@code_instead (*) SourceCode Any
97+
@code_instead (*) Any SourceCode
98+
@code_instead (*) SourceCode SourceCode
99+
100+
@code_instead (+) SourceCode Any
101+
@code_instead (+) Any SourceCode
102+
@code_instead (+) SourceCode SourceCode
103+
104+
@code_instead (%) SourceCode Any
105+
@code_instead (%) Any SourceCode
106+
@code_instead (%) SourceCode SourceCode
107+
108+
@code_instead abs SourceCode
109+
110+
@code_instead chop SourceCode
111+
112+
@code_instead chop SourceCode
113+
83114
@code_instead coalesce SourceCode Vararg{Any}
84115

85116
@code_instead QueryOperators.drop SourceCode Integer
@@ -112,6 +143,9 @@ end
112143

113144
@code_instead QueryOperators.join SourceCode SourceCode Any Expr Any Expr Any Expr
114145

146+
@code_instead max SourceCode Vararg{Any}
147+
@code_instead min SourceCode Vararg{Any}
148+
115149
@code_instead length SourceCode
116150

117151
@code_instead QueryOperators.map SourceCode Any Expr
@@ -129,10 +163,16 @@ end
129163
@code_instead startswith Any SourceCode
130164
@code_instead startswith SourceCode SourceCode
131165

166+
@code_instead string SourceCode Any
167+
@code_instead string Any SourceCode
168+
@code_instead string SourceCode SourceCode
169+
132170
@code_instead QueryOperators.take SourceCode Any
133171

134172
@code_instead QueryOperators.thenby SourceCode Any Expr
135173

136174
@code_instead QueryOperators.thenby_descending SourceCode Any Expr
137175

138176
@code_instead QueryOperators.unique SourceCode Any Expr
177+
178+
@code_instead uppercase SourceCode

src/model_row.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ end
66

77
# The model row pass will build and modify a model of a row
88
function model_row(call)
9-
model_row_call(split_call(call)...)
9+
arguments, keywords = split_call(call)
10+
model_row_call(arguments...; keywords...)
1011
end
1112

1213
# Very few functions modify the row model, so leave the row unchanged by default
@@ -16,7 +17,9 @@ end
1617

1718
# Create a model row when getproperty is called on a Database
1819
function get_column(source_row, column_name)
19-
SourceCode(source_row.source, Expr(:call, getproperty, source_row, column_name))
20+
SourceCode(source_row.source,
21+
Expr(:call, getproperty, source_row, column_name)
22+
)
2023
end
2124

2225
function model_row_call(::typeof(getproperty), source_tables::Database, table_name)

src/show.jl

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@ function tight_infix(io, call, argument1, argument2)
1717
print(io, argument2)
1818
end
1919

20-
function infix(io, call, argument1, argument2)
20+
function infix(io, call, argument1, argument2, arguments...)
2121
print(io, argument1)
2222
print(io, ' ')
2323
print(io, call)
2424
print(io, ' ')
25-
print(io, argument2)
25+
infix(io, call, argument2, arguments...)
26+
end
27+
28+
function infix(io, call, argument1)
29+
print(io, argument1)
2630
end
2731

2832
function head_call_tail(io, call, argument1, arguments...)
@@ -70,8 +74,18 @@ function show(io::IO, sql_expression::SQLExpression)
7074
tight_infix(io, call, arguments...)
7175
elseif call === :(=)
7276
infix(io, call, arguments...)
77+
elseif call === :<
78+
infix(io, call, arguments...)
7379
elseif call === Symbol("<>")
7480
infix(io, call, arguments...)
81+
elseif call === Symbol("||")
82+
infix(io, call, arguments...)
83+
elseif call === :*
84+
infix(io, call, arguments...)
85+
elseif call === :+
86+
infix(io, call, arguments...)
87+
elseif call === :%
88+
infix(io, call, arguments...)
7589
elseif call === :AND
7690
infix(io, call, arguments...)
7791
elseif call === :AS
@@ -110,6 +124,8 @@ function show(io::IO, sql_expression::SQLExpression)
110124
call_tail_head(io, call, arguments...)
111125
elseif call === Symbol("SELECT DISTINCT")
112126
call_tail_head(io, call, arguments...)
127+
elseif call === :WHERE
128+
infix(io, call, arguments...)
113129
else
114130
print(io, call)
115131
print(io, '(')

src/translate.jl

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ function translate(source_row::SourceRow; primary = true)
1313
source_row.table_name
1414
end
1515
function translate(call::Expr; primary = true)
16-
translate_call(split_call(call)...; primary = primary)
16+
arguments, keywords = split_call(call)
17+
# TODO: figure out a way for users to pass a keyword named primary
18+
translate_call(arguments...; primary = primary, keywords...)
1719
end
1820

1921
# A 1-1 mapping between Julia functions and SQL functions
20-
function translate_default(location, a_function, SQL_call)
22+
function translate_default(location, function_type, SQL_call)
2123
result = :(
22-
function translate_call(::typeof($a_function), arguments...; primary = true)
24+
function translate_call($function_type, arguments...; primary = true)
2325
$SQLExpression($SQL_call, $map_unrolled(
2426
argument -> $translate(argument; primary = primary),
2527
arguments
@@ -34,15 +36,23 @@ macro translate_default(a_function, SQL_call)
3436
translate_default(__source__, a_function, SQL_call) |> esc
3537
end
3638

37-
@translate_default (==) :(=)
39+
@translate_default ::typeof(==) :(=)
3840

39-
@translate_default (!=) Symbol("<>")
41+
@translate_default ::typeof(!=) Symbol("<>")
4042

41-
@translate_default (!) :NOT
43+
@translate_default ::typeof(!) :NOT
4244

43-
@translate_default (&) :AND
45+
@translate_default ::typeof(&) :AND
4446

45-
@translate_default (|) :OR
47+
@translate_default ::typeof(|) :OR
48+
49+
@translate_default ::typeof(*) :*
50+
51+
@translate_default ::typeof(+) :+
52+
53+
@translate_default ::typeof(%) :%
54+
55+
@translate_default ::typeof(abs) :ABS
4656

4757
function as(pair; primary = true)
4858
SQLExpression(:AS,
@@ -51,9 +61,11 @@ function as(pair; primary = true)
5161
)
5262
end
5363

54-
@translate_default coalesce :COALESCE
64+
@translate_default ::Type{Char} :CHAR
65+
66+
@translate_default ::typeof(coalesce) :COALESCE
5567

56-
@translate_default QueryOperators.drop :OFFSET
68+
@translate_default ::typeof(QueryOperators.drop) :OFFSET
5769

5870
function translate_call(::typeof(QueryOperators.filter), iterator, call, call_expression; primary = true)
5971
SQLExpression(:WHERE,
@@ -88,15 +100,15 @@ function translate_call(::typeof(QueryOperators.groupby), ungrouped, group_funct
88100
)
89101
end
90102

91-
@translate_default if_else :CASE
103+
@translate_default ::typeof(if_else) :CASE
92104

93-
@translate_default in :IN
105+
@translate_default ::typeof(in) :IN
94106

95-
@translate_default isequal Symbol("IS NOT DISTINCT FROM")
107+
@translate_default ::typeof(isequal) Symbol("IS NOT DISTINCT FROM")
96108

97-
@translate_default isless :<
109+
@translate_default ::typeof(isless) :<
98110

99-
@translate_default ismissing Symbol("IS NULL")
111+
@translate_default ::typeof(ismissing) Symbol("IS NULL")
100112

101113
function translate_call(::typeof(QueryOperators.join), source1, source2, key1, key1_expression, key2, key2_expression, combine, combine_expression; primary = true)
102114
model_row_1 = model_row(source1)
@@ -122,7 +134,9 @@ function translate_call(::typeof(QueryOperators.join), source1, source2, key1, k
122134
)
123135
end
124136

125-
@translate_default length :COUNT
137+
@translate_default ::typeof(length) :COUNT
138+
139+
@translate_default ::typeof(lowercase) :LOWER
126140

127141
function translate_call(::typeof(QueryOperators.map), select_table, call, call_expression; primary = true)
128142
SQLExpression(
@@ -134,6 +148,10 @@ function translate_call(::typeof(QueryOperators.map), select_table, call, call_e
134148
)
135149
end
136150

151+
@translate_default ::typeof(max) :max
152+
153+
@translate_default ::typeof(min) :min
154+
137155
translate_call(::typeof(occursin), needle::AbstractString, haystack; primary = true) =
138156
SQLExpression(
139157
:LIKE,
@@ -147,7 +165,7 @@ translate_call(::typeof(occursin), needle::Regex, haystack; primary = true) =
147165
# * => %, . => _
148166
replace(replace(needle.pattern, r"(?<!\\)\.\*" => "%"), r"(?<!\\)\." => "_")
149167
)
150-
@translate_default occursin :LIKE
168+
@translate_default ::typeof(occursin) :LIKE
151169

152170
function translate_call(::typeof(QueryOperators.orderby), unordered, key_function, key_function_expression; primary = true)
153171
SQLExpression(Symbol("ORDER BY"),
@@ -172,7 +190,9 @@ translate_call(::typeof(startswith), full, prefix::AbstractString; primary = pri
172190
string(prefix, '%')
173191
)
174192

175-
@translate_default QueryOperators.take :LIMIT
193+
@translate_default ::typeof(string) :||
194+
195+
@translate_default ::typeof(QueryOperators.take) :LIMIT
176196

177197
function translate_call(::typeof(QueryOperators.thenby), unordered, key_function, key_function_expression; primary = true)
178198
original = translate(unordered; primary = primary)
@@ -194,3 +214,5 @@ function translate_call(::typeof(QueryOperators.unique), repeated, key_function,
194214
result = translate(repeated; primary = primary)
195215
SQLExpression(Symbol(string(result.call, " DISTINCT")), result.arguments...)
196216
end
217+
218+
@translate_default ::typeof(uppercase) :UPPER

src/utilities.jl

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,22 @@ function as_symbols(them)
4040
map_unrolled(Symbol, (them...,))
4141
end
4242

43+
split_keyword(keyword::Expr) =
44+
if keyword.head === :kw
45+
Pair(keyword.args[1], keyword.args[2])
46+
else
47+
error("Cannot split keyword $keyword")
48+
end
49+
4350
# Split a function call into its pieces
4451
# Normalize non-function-like patterns into function calls
4552
function split_call(call_expression::Expr)
46-
if @capture call_expression call_(arguments__)
47-
if call == ifelse
48-
if_else
49-
else
50-
call
51-
end, arguments...
52-
elseif @capture call_expression left_ && right_
53-
&, left, right
54-
elseif @capture call_expression left_ || right_
55-
|, left, right
56-
elseif @capture call_expression if condition_ yes_ else no_ end
57-
if_else, condition, yes, no
53+
if @capture call_expression call_(arguments__; keywords__)
54+
(call, arguments...), (; map(split_keyword, keywords)...)
55+
elseif @capture call_expression call_(arguments__)
56+
(call, arguments...), ()
5857
else
59-
error("Cannot split $call_expression")
58+
error("$call_expression is not a function call")
6059
end
6160
end
6261

@@ -79,3 +78,20 @@ function if_else(switch, yes, no)
7978
ifelse(switch, yes, no)
8079
end
8180
export if_else
81+
82+
"""
83+
type_of(it)
84+
85+
`typeof` that you can add methods to.
86+
87+
```jldoctest
88+
julia> using QuerySQLite
89+
90+
julia> type_of('a')
91+
Char
92+
```
93+
"""
94+
function type_of(it)
95+
typeof(it)
96+
end
97+
export type_of

test/Chinook_Sqlite.sqlite

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)