Skip to content

Commit d1ae6ce

Browse files
committed
refactor lexer, parser and adapters
1 parent 581b514 commit d1ae6ce

23 files changed

+1533
-957
lines changed

bench.exs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@ import SQL
22
import Ecto.Query
33
defmodule SQL.Repo do
44
use Ecto.Repo, otp_app: :sql, adapter: Ecto.Adapters.Postgres
5+
use SQL, adapter: SQL.Adapters.Postgres, repo: __MODULE__
6+
7+
def test() do
8+
~SQL[select 1]
9+
|> SQL.map(fn row -> row end)
10+
|> Enum.to_list
11+
end
512
end
613
Application.put_env(:sql, :ecto_repos, [SQL.Repo])
714
Application.put_env(:sql, SQL.Repo, username: "postgres", password: "postgres", hostname: "localhost", database: "sql_test#{System.get_env("MIX_TEST_PARTITION")}", pool: Ecto.Adapters.SQL.Sandbox, pool_size: 10)
815
SQL.Repo.__adapter__().storage_up(SQL.Repo.config())
916
SQL.Repo.start_link()
10-
sql = ~SQL[with recursive temp (n, fact) as (select 0, 1 union all select n+1, (n+1)*fact from temp where n < 9)]
1117
query = "temp" |> recursive_ctes(true) |> with_cte("temp", as: ^union_all(select("temp", [t], %{n: 0, fact: 1}), ^where(select("temp", [t], [t.n+1, t.n+1*t.fact]), [t], t.n < 9))) |> select([t], [t.n])
18+
sql = ~SQL[with recursive temp (n, fact) as (select 0, 1 union all select n+1, (n+1)*fact from temp where n < 9)]
1219
result = Tuple.to_list(SQL.Lexer.lex("with recursive temp (n, fact) as (select 0, 1 union all select n+1, (n+1)*fact from temp where n < 9)"))
1320
tokens = Enum.at(result, -1)
1421
context = Map.merge(Enum.at(result, 1), %{sql_lock: nil, module: SQL.Adapters.ANSI})
@@ -18,12 +25,18 @@ Benchee.run(
1825
"comptime to_string" => fn _ -> to_string(sql) end,
1926
"comptime to_sql" => fn _ -> SQL.to_sql(sql) end,
2027
"comptime inspect" => fn _ -> inspect(sql) end,
21-
"comptime ecto" => fn _ -> SQL.Repo.to_sql(:all, query) end,
2228
"lex" => fn _ -> SQL.Lexer.lex("with recursive temp (n, fact) as (select 0, 1 union all select n+1, (n+1)*fact from temp where n < 9)") end,
2329
"parse" => fn _ -> SQL.Parser.parse(tokens, context) end,
24-
"iodata" => fn _ -> pcontext.module.to_iodata(ptokens, pcontext, 0, []) end,
25-
"token_to_string" => fn _ -> pcontext.module.token_to_string(ptokens) end,
30+
"iodata" => fn _ -> pcontext.module.to_iodata(ptokens, pcontext) end,
31+
"format" => fn _ -> SQL.Format.to_iodata(ptokens, pcontext) end,
32+
"lex+parse+iodata" => fn _ ->
33+
{:ok, _, tokens} = SQL.Lexer.lex("with recursive temp (n, fact) as (select 0, 1 union all select n+1, (n+1)*fact from temp where n < 9)")
34+
{:ok, pcontext, tokens} = SQL.Parser.parse(tokens, context)
35+
pcontext.module.to_iodata(tokens, pcontext)
36+
end,
37+
"parse" => fn _ -> SQL.parse("with recursive temp (n, fact) as (select 0, 1 union all select n+1, (n+1)*fact from temp where n < 9)") end,
2638
"runtime dynamic" => fn _ -> ~SQL[from users] |> ~SQL[select *] |> ~SQL[where id = {{1+2*3}}] end,
39+
"runtime ecto dynamic" => fn _ -> SQL.Repo.to_sql(:all, from("users") |> select([t], t.id) |> where([t], t.id == ^(1+2*3))) end,
2740
"runtime to_string" => fn _ -> to_string(~SQL[with recursive temp (n, fact) as (select 0, 1 union all select n+1, (n+1)*fact from temp where n < 9)]) end,
2841
"runtime to_sql" => fn _ -> SQL.to_sql(~SQL[with recursive temp (n, fact) as (select 0, 1 union all select n+1, (n+1)*fact from temp where n < 9)]) end,
2942
"runtime inspect" => fn _ -> inspect(~SQL[with recursive temp (n, fact) as (select 0, 1 union all select n+1, (n+1)*fact from temp where n < 9)]) end,

lib/adapters/ansi.ex

Lines changed: 0 additions & 276 deletions
Original file line numberDiff line numberDiff line change
@@ -6,281 +6,5 @@ defmodule SQL.Adapters.ANSI do
66
A SQL adapter for [ANSI](https://blog.ansi.org/sql-standard-iso-iec-9075-2023-ansi-x3-135/).
77
"""
88
@moduledoc since: "0.2.0"
9-
109
use SQL.Token
11-
12-
@doc false
13-
def token_to_string(value, mod \\ __MODULE__)
14-
def token_to_string(value, _mod) when is_struct(value) do
15-
to_string(value)
16-
end
17-
def token_to_string({:as, [], [left, right]}, mod) do
18-
"#{mod.token_to_string(left)} #{mod.token_to_string(right)}"
19-
end
20-
def token_to_string({tag, _, [left]}, mod) when tag in ~w[asc desc isnull notnull]a do
21-
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)}"
22-
end
23-
def token_to_string({:not=tag, _, [{t,_,_} =left]}, mod) when t == :ident do
24-
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)}"
25-
end
26-
def token_to_string({:fn, _, [left, right]}, mod) do
27-
"#{mod.token_to_string(left)}#{mod.token_to_string(right)}"
28-
end
29-
def token_to_string({:dot, _, [left, right]}, mod) do
30-
"#{mod.token_to_string(left)}.#{mod.token_to_string(right)}"
31-
end
32-
def token_to_string({tag, [{:type, :operator}|_], [{:not=t, _, left}, right]}, mod) do
33-
"#{mod.token_to_string(left)} #{mod.token_to_string(t)} #{mod.token_to_string(tag)} #{mod.token_to_string(right)}"
34-
end
35-
def token_to_string({tag, [{:type, :operator}|_], [left, right]}, mod) do
36-
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)} #{mod.token_to_string(right)}"
37-
end
38-
def token_to_string({:ident, [{:tag, tag}|_], [{:paren, _, _} = value]}, mod) do
39-
"#{mod.token_to_string(tag)}#{mod.token_to_string(value)}"
40-
end
41-
def token_to_string({:ident, [{:tag, tag}|_], [{:numeric, _, _} = value]}, mod) do
42-
"#{mod.token_to_string(tag)} #{mod.token_to_string(value)}"
43-
end
44-
def token_to_string({:ident, [{:tag, tag}|_], _}, mod) do
45-
mod.token_to_string(tag)
46-
end
47-
def token_to_string({tag, [{:type, :reserved}|_], [{:paren, _, _} = value]}, mod) when tag not in ~w[on in select]a do
48-
"#{mod.token_to_string(tag)}#{mod.token_to_string(value)}"
49-
end
50-
def token_to_string({tag, [{:type, :reserved}|_], []}, mod) do
51-
mod.token_to_string(tag)
52-
end
53-
def token_to_string({tag, _, [left, {:all = t, _, right}]}, mod) when tag in ~w[union except intersect]a do
54-
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)} #{mod.token_to_string(t)} #{mod.token_to_string(right)}"
55-
end
56-
def token_to_string({:between = tag, _, [{:not = t, _, right}, left]}, mod) do
57-
"#{mod.token_to_string(right)} #{mod.token_to_string(t)} #{mod.token_to_string(tag)} #{mod.token_to_string(left)}"
58-
end
59-
def token_to_string({:binding, _, [idx]}, _mod) when is_integer(idx) do
60-
"?"
61-
end
62-
def token_to_string({:binding, _, value}, _mod) do
63-
"{{#{value}}}"
64-
end
65-
def token_to_string({:comment, _, value}, _mod) do
66-
"--#{value}"
67-
end
68-
def token_to_string({:comments, _, value}, _mod) do
69-
"\\*#{value}*\\"
70-
end
71-
def token_to_string({:double_quote, [{:prefix, prefix}|_], value}, _mod) do
72-
"#{prefix}\"#{value}\""
73-
end
74-
def token_to_string({:double_quote, _, value}, _mod) do
75-
"\"#{value}\""
76-
end
77-
def token_to_string({:quote, [{:prefix, prefix}|_], value}, _mod) do
78-
"#{prefix}'#{value}'"
79-
end
80-
def token_to_string({:quote, _, value}, _mod) do
81-
"'#{value}'"
82-
end
83-
def token_to_string({:backtick, [{:prefix, prefix}|_], value}, _mod) do
84-
"#{prefix}`#{value}`"
85-
end
86-
def token_to_string({:backtick, _, value}, _mod) do
87-
"`#{value}`"
88-
end
89-
def token_to_string({:paren, _, value}, mod) do
90-
"(#{mod.token_to_string(value)})"
91-
end
92-
def token_to_string({:bracket, _, value}, mod) do
93-
"[#{mod.token_to_string(value)}]"
94-
end
95-
def token_to_string({:colon, _, value}, mod) do
96-
"#{mod.token_to_string(value)};"
97-
end
98-
def token_to_string({:comma, _, value}, mod) do
99-
"#{mod.token_to_string(value)},"
100-
end
101-
def token_to_string({tag, _, value}, _mod) when tag in ~w[ident numeric special]a do
102-
"#{value}"
103-
end
104-
def token_to_string(value, _mod) when is_atom(value) do
105-
"#{value}"
106-
end
107-
def token_to_string(value, _mod) when is_binary(value) do
108-
"'#{value}'"
109-
end
110-
def token_to_string(value, _mod) when is_integer(value) do
111-
[value]
112-
end
113-
def token_to_string({tag, _, [left, right]}, mod) when tag in ~w[like ilike union except intersect between and or is not in cursor for to]a do
114-
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)} #{mod.token_to_string(right)}"
115-
end
116-
def token_to_string({tag, _, []}, mod) do
117-
mod.token_to_string(tag)
118-
end
119-
def token_to_string({tag, [{:type, :reserved}|_], values=[_|_]}, mod) do
120-
"#{mod.token_to_string(tag)} #{mod.token_to_string(values)}"
121-
end
122-
def token_to_string({tag, [{:type, :non_reserved}|_], values=[_|_]}, mod) when tag != :ident do
123-
"#{mod.token_to_string(tag)} #{mod.token_to_string(values)}"
124-
end
125-
def token_to_string(values, mod) do
126-
values
127-
|> Enum.reduce([], fn
128-
[], acc -> acc
129-
{:comma, _, _} = token, acc -> [acc, mod.token_to_string(token), " "]
130-
token, [] -> mod.token_to_string(token)
131-
token, [_, _, " "] = acc -> [acc, mod.token_to_string(token)]
132-
token, [_, " "] = acc -> [acc, mod.token_to_string(token)]
133-
token, acc -> [acc, " ", mod.token_to_string(token)]
134-
end)
135-
end
136-
137-
@doc false
138-
def to_iodata({tag, _, values}, context, indent) when tag in ~w[inner outer left right full natural cross]a do
139-
[context.module.to_iodata(tag, context, indent),context.module.to_iodata(values, context, indent, [?\s])]
140-
end
141-
def to_iodata({tag, _, values}, context, indent) when tag in ~w[join]a do
142-
v = Enum.reduce(values, [], fn token, acc -> [acc, ?\s, context.module.to_iodata(token, context, indent)] end)
143-
144-
[indention(indent), context.module.to_iodata(tag, context, indent) | [v, ?\n]]
145-
end
146-
def to_iodata({tag, _, values}, context, indent) when tag in ~w[select from join where group having window order limit offset fetch]a do
147-
v = Enum.reduce(values, [?\n], fn token, acc -> [acc, indention(indent+1), context.module.to_iodata(token, context, indent+1), ?\n] end)
148-
149-
[indention(indent), context.module.to_iodata(tag, context, indent) | v]
150-
end
151-
def to_iodata({:as, [], [left, right]}, context, indent) do
152-
[context.module.to_iodata(left, context, indent),?\s|context.module.to_iodata(right, context, indent)]
153-
end
154-
def to_iodata({tag, _, [left]}, context, indent) when tag in ~w[asc desc isnull notnull]a do
155-
[context.module.to_iodata(left, context, indent),?\s|context.module.to_iodata(tag, context, indent)]
156-
end
157-
def to_iodata({:fn, _, [left, right]}, context, indent) do
158-
[context.module.to_iodata(left, context, indent)|context.module.to_iodata(right, context, indent)]
159-
end
160-
def to_iodata({:dot, _, [left, right]}, context, indent) do
161-
[context.module.to_iodata(left, context, indent),?\.|context.module.to_iodata(right, context, indent)]
162-
end
163-
def to_iodata({tag, [{:type, :operator}|_], [{:not=t, _, left}, right]}, context, indent) do
164-
[context.module.to_iodata(left, context, indent),?\s,context.module.to_iodata(t, context, indent),?\s,context.module.to_iodata(tag, context, indent),?\s|context.module.to_iodata(right, context, indent)]
165-
end
166-
def to_iodata({tag, [{:type, :operator}|_], [left, {:paren, _, _} = right]}, context, indent) do
167-
[context.module.to_iodata(left, context, indent),?\s,context.module.to_iodata(tag, context, indent),?\s|context.module.to_iodata(right, context, indent)]
168-
end
169-
def to_iodata({tag, [{:type, :operator}|_], [left, right]}, context, indent) do
170-
[context.module.to_iodata(left, context, indent),?\s,context.module.to_iodata(tag, context, indent),?\s|context.module.to_iodata(right, context, indent)]
171-
end
172-
def to_iodata({tag, [{:type, :operator}|_], []}, context, indent) do
173-
[context.module.to_iodata(tag, context, indent)]
174-
end
175-
def to_iodata({:ident, [{:tag, tag}|_], [{:paren, _, _} = value]}, context, indent) do
176-
[context.module.to_iodata(tag, context, indent)|context.module.to_iodata(value, context, indent)]
177-
end
178-
def to_iodata({:ident, [{:tag, tag}|_], [{:numeric, _, _} = value]}, context, indent) do
179-
[context.module.to_iodata(tag, context, indent),?\s|context.module.to_iodata(value, context, indent)]
180-
end
181-
def to_iodata({:ident, [{:tag, tag}|_], _}, context, indent) do
182-
context.module.to_iodata(tag, context, indent)
183-
end
184-
def to_iodata({tag, [{:type, :reserved}|_], [{:paren, _, _} = value]}, context, indent) when tag not in ~w[on in select]a do
185-
[context.module.to_iodata(tag, context, indent)|context.module.to_iodata(value, context, indent)]
186-
end
187-
def to_iodata({tag, [{:type, :reserved}|_], []}, context, indent) do
188-
[?\s, context.module.to_iodata(tag, context, indent)]
189-
end
190-
def to_iodata({tag, _, [left, {:all = t, _, right}]}, context, indent) when tag in ~w[union except intersect]a do
191-
[context.module.to_iodata(left, context, indent), indention(indent), context.module.to_iodata(tag, context, indent),?\s,context.module.to_iodata(t, context, indent),?\n|context.module.to_iodata(right, context, indent)]
192-
end
193-
def to_iodata({:between = tag, _, [{:not = t, _, right}, left]}, context, indent) do
194-
[context.module.to_iodata(right, context, indent),?\s,context.module.to_iodata(t, context, indent),?\s,context.module.to_iodata(tag, context, indent),?\s|context.module.to_iodata(left, context, indent)]
195-
end
196-
def to_iodata({:binding, _, [idx]}, %{format: true, binding: binding}, _indent) do
197-
[?{,?{,Macro.to_string(Enum.at(binding, idx-1))|[?},?}]]
198-
end
199-
def to_iodata({:binding, _, _}, _context, _indent) do
200-
[??]
201-
end
202-
def to_iodata({:comment, _, value}, _context, _indent) do
203-
[?-,?-|value]
204-
end
205-
def to_iodata({:comments, _, value}, _context, _indent) do
206-
[?\\,?*,value|[?*, ?\\]]
207-
end
208-
def to_iodata({:double_quote, [{:prefix, prefix}|_], value}=node, context, _indent) do
209-
case node in context.errors do
210-
true -> [[prefix,?",:red,value|[:reset, ?"]]]
211-
false -> [prefix, ?", value, ?"]
212-
end
213-
end
214-
def to_iodata({:double_quote, _, value}=node, context, _indent) do
215-
case node in context.errors do
216-
true -> [[?",:red,value|[:reset, ?"]]]
217-
false -> [?", value, ?"]
218-
end
219-
end
220-
def to_iodata({:quote, [{:prefix, prefix}|_], value}, _context, _indent) do
221-
[prefix,?',value|[?']]
222-
end
223-
def to_iodata({:quote, _, value}, _context, _indent) do
224-
[?',value|[?']]
225-
end
226-
def to_iodata({:backtick, [{:prefix, prefix}|_], value}, _context, _indent) do
227-
[prefix,?`,value|[?`]]
228-
end
229-
def to_iodata({:backtick, _, value}, _context, _indent) do
230-
[?`,value|[?`]]
231-
end
232-
def to_iodata({:paren, _, [{_,[{:type, :reserved}|_],_}|_] = value}, context, indent) do
233-
[?(,?\n, context.module.to_iodata(value, context, indent+1)|?)]
234-
end
235-
def to_iodata({:paren, _, value}, context, indent) do
236-
[?(,context.module.to_iodata(value, context, indent)|[?)]]
237-
end
238-
def to_iodata({:bracket, _, value}, context, indent) do
239-
[?[,context.module.to_iodata(value, context, indent)|[?]]]
240-
end
241-
def to_iodata({:colon, _, value}, context, indent) do
242-
[context.module.to_iodata(value, context, indent)|[?;,?\n]]
243-
end
244-
def to_iodata({:comma, _, value}, context, indent) do
245-
[context.module.to_iodata(value, context, indent), ?,, ?\s]
246-
end
247-
def to_iodata({tag, _, value} = node, context, _indent) when tag in ~w[ident numeric special]a do
248-
case node in context.errors do
249-
true -> [:red, value, :reset]
250-
false -> value
251-
end
252-
end
253-
def to_iodata(value, _context, _indent) when is_atom(value) do
254-
~c"#{value}"
255-
end
256-
def to_iodata(value, _context, _indent) when is_binary(value) do
257-
[?',value|[?']]
258-
end
259-
def to_iodata(value, _context, _indent) when is_integer(value) do
260-
[value]
261-
end
262-
def to_iodata(value, _context, _indent) when is_struct(value) do
263-
to_string(value)
264-
end
265-
def to_iodata({tag, _, [left, right]}, context, indent) when tag in ~w[like ilike union except intersect between and or is not in cursor for to]a do
266-
[context.module.to_iodata(left, context, indent),?\s,context.module.to_iodata(tag, context, indent),?\s|context.module.to_iodata(right, context, indent)]
267-
end
268-
def to_iodata({tag, [{:type, :reserved}|_], values=[_|_]}, context, indent) do
269-
[context.module.to_iodata(tag, context, indent),?\s|context.module.to_iodata(values, context, indent)]
270-
end
271-
def to_iodata({tag, [{:type, :non_reserved}|_], values=[_|_]}, context, indent) when tag != :ident do
272-
[context.module.to_iodata(tag, context, indent),?\s|context.module.to_iodata(values, context, indent)]
273-
end
274-
def to_iodata({tag, _, []}, context, indent) do
275-
[indention(indent), context.module.to_iodata(tag, context, indent)]
276-
end
277-
def to_iodata([[{_,_,_}|_]|_]=tokens, context, indent) do
278-
to_iodata(tokens, context, indent, [])
279-
end
280-
def to_iodata([{_,_,_}|_]=tokens, context, indent) do
281-
to_iodata(tokens, context, indent, [])
282-
end
283-
def to_iodata([]=tokens, _context, _indent) do
284-
tokens
285-
end
28610
end

lib/adapters/mysql.ex

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,5 @@ defmodule SQL.Adapters.MySQL do
66
A SQL adapter for [MySQL](https://www.mysql.com).
77
"""
88
@moduledoc since: "0.2.0"
9-
109
use SQL.Token
11-
12-
@doc false
13-
def token_to_string(value, mod \\ __MODULE__)
14-
def token_to_string(token, mod), do: SQL.Adapters.ANSI.token_to_string(token, mod)
15-
16-
@doc false
17-
def to_iodata(token, context, indent), do: SQL.Adapters.ANSI.to_iodata(token, context, indent)
1810
end

lib/adapters/postgres.ex

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,16 @@ defmodule SQL.Adapters.Postgres do
66
A SQL adapter for [PostgreSQL](https://www.postgresql.org).
77
"""
88
@moduledoc since: "0.2.0"
9-
109
use SQL.Token
1110

12-
@doc false
13-
def token_to_string(value, mod \\ __MODULE__)
14-
def token_to_string({:in, _, [{:not, _, left}, {:binding, _, _} = right]}, mod), do: "#{mod.token_to_string(left)} != ANY(#{mod.token_to_string(right)})"
15-
def token_to_string({:in, _, [left, {:binding, _, _} = right]}, mod), do: "#{mod.token_to_string(left)} = ANY(#{mod.token_to_string(right)})"
16-
def token_to_string({:binding, _, [idx]}, _mod) when is_integer(idx), do: "$#{idx}"
17-
def token_to_string({tag, _, [left, right]}, mod) when tag in ~w[>>=]a do
18-
"#{mod.token_to_string(left)} #{mod.token_to_string(tag)} #{mod.token_to_string(right)}"
19-
end
20-
def token_to_string(token, mod), do: SQL.Adapters.ANSI.token_to_string(token, mod)
21-
22-
@doc false
23-
def to_iodata({:in, _, [{:not=t, _, left}, {:binding, _, [idx]}]}, %{format: true, binding: binding} = context, indent) do
24-
[context.module.to_iodata(left, context, indent),?\s,context.module.to_iodata(t, context, indent),?\s, ?{,?{,Macro.to_string(Enum.at(binding, idx-1)),?},?}]
25-
end
26-
def to_iodata({:in, _, [{:not, _, left}, {:binding, _, _}=right]}, context, indent) do
27-
[context.module.to_iodata(left, context, indent), ?!, ?=, ?A,?N,?Y,?(, context.module.to_iodata(right, context, indent), ?)]
28-
end
29-
def to_iodata({:in, _, [left, {:binding, _, [idx]}]}, %{format: true, binding: binding} = context, indent) do
30-
[context.module.to_iodata(left, context, indent), ?\s, ?{,?{,Macro.to_string(Enum.at(binding, idx-1)),?},?}]
31-
end
32-
def to_iodata({:in, _, [left, {:binding, _, _} = right]}, context, indent) do
33-
[context.module.to_iodata(left, context, indent), ?=, ?A,?N,?Y,?(, context.module.to_iodata(right, context, indent), ?)]
11+
defp to_iodata({:in, m, [{:not, _, left}, {:binding, _, [idx]}]}, format, case, acc) do
12+
to_iodata(left, format, case, indention(["!= ANY($#{idx})"|acc], format, m))
3413
end
35-
def to_iodata({:binding, _, [idx]}, %{format: true, binding: binding}, _indent) do
36-
[?{,?{,Macro.to_string(Enum.at(binding, idx-1))|[?},?}]]
14+
defp to_iodata({:in, m, [left, {:binding, _, [idx]} ]}, format, case, acc) do
15+
to_iodata(left, format, case, indention(["= ANY($#{idx})"|acc], format, m))
3716
end
38-
def to_iodata({:binding, _, [idx]}, _context, _indent) do
39-
~c"$#{idx}"
17+
defp to_iodata({:binding, m, [idx]}, format, _case, acc) do
18+
indention(["$#{idx}"|acc], format, m)
4019
end
41-
def to_iodata(token, context, indent), do: SQL.Adapters.ANSI.to_iodata(token, context, indent)
20+
defp to_iodata(token, format, case, acc), do: __to_iodata__(token, format, case, acc)
4221
end

0 commit comments

Comments
 (0)