Current section
Files
Jump to
Current section
Files
src/sqlode@query_analyzer@type_inference.erl
-module(sqlode@query_analyzer@type_inference).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/sqlode/query_analyzer/type_inference.gleam").
-export([scope/4, infer_expr_type/2]).
-export_type([inferred_type/0, scope/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" IR-driven type inference.\n"
"\n"
" `infer_expr_type` walks a `query_ir.Expr` and returns the inferred\n"
" `#(ScalarType, nullable)` pair, resolving column references against\n"
" the supplied catalog and table scope. It replaces the previous\n"
" heuristic path that operated on raw `List(lexer.Token)`.\n"
"\n"
" When the IR contains a `RawExpr` node — or an inner construct that\n"
" inference can't make sense of — the function returns an\n"
" `UnsupportedExpression` error tied to the raw token fragment so\n"
" the operator gets an explicit diagnostic pointing at the IR gap,\n"
" never a silent fallback to `StringType`.\n"
).
-type inferred_type() :: {inferred_type, sqlode@model:scalar_type(), boolean()}.
-type scope() :: {scope,
binary(),
sqlode@model:catalog(),
list(binary()),
list(binary())}.
-file("src/sqlode/query_analyzer/type_inference.gleam", 45).
-spec scope(binary(), sqlode@model:catalog(), list(binary()), list(binary())) -> scope().
scope(Query_name, Catalog, In_scope_tables, Nullable_tables) ->
{scope, Query_name, Catalog, In_scope_tables, Nullable_tables}.
-file("src/sqlode/query_analyzer/type_inference.gleam", 132).
-spec ok(sqlode@model:scalar_type(), boolean()) -> {ok, inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
ok(Scalar, Nullable) ->
{ok, {inferred_type, Scalar, Nullable}}.
-file("src/sqlode/query_analyzer/type_inference.gleam", 139).
-spec number_type(binary()) -> sqlode@model:scalar_type().
number_type(N) ->
case (gleam_stdlib:contains_string(N, <<"."/utf8>>) orelse gleam_stdlib:contains_string(
N,
<<"e"/utf8>>
))
orelse gleam_stdlib:contains_string(N, <<"E"/utf8>>) of
true ->
float_type;
false ->
int_type
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 154).
-spec resolve_column(scope(), gleam@option:option(binary()), binary()) -> {ok,
inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
resolve_column(Scope, Table, Name) ->
case Table of
{some, T} ->
case sqlode@query_analyzer@context:find_column(
erlang:element(3, Scope),
T,
Name
) of
{some, Col} ->
ok(
erlang:element(3, Col),
erlang:element(4, Col) orelse gleam@list:contains(
erlang:element(5, Scope),
T
)
);
none ->
{error,
{column_not_found, erlang:element(2, Scope), T, Name}}
end;
none ->
case sqlode@query_analyzer@context:find_column_in_tables(
erlang:element(3, Scope),
erlang:element(4, Scope),
Name
) of
{ok, {some, {Found_table, Col@1}}} ->
ok(
erlang:element(3, Col@1),
erlang:element(4, Col@1) orelse gleam@list:contains(
erlang:element(5, Scope),
Found_table
)
);
{ok, none} ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<<<"unresolved column reference \""/utf8,
Name/binary>>/binary,
"\""/utf8>>}};
{error, Matching} ->
{error,
{ambiguous_column_name,
erlang:element(2, Scope),
Name,
Matching}}
end
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 292).
-spec ok_unknown() -> {ok, inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
ok_unknown() ->
{ok, {inferred_type, int_type, false}}.
-file("src/sqlode/query_analyzer/type_inference.gleam", 296).
-spec ok_unknown_nullable() -> {ok, inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
ok_unknown_nullable() ->
{ok, {inferred_type, int_type, true}}.
-file("src/sqlode/query_analyzer/type_inference.gleam", 300).
-spec merge_numeric(sqlode@model:scalar_type(), sqlode@model:scalar_type()) -> {ok,
sqlode@model:scalar_type()} |
{error, nil}.
merge_numeric(A, B) ->
case {A, B} of
{int_type, int_type} ->
{ok, int_type};
{float_type, float_type} ->
{ok, float_type};
{int_type, float_type} ->
{ok, float_type};
{float_type, int_type} ->
{ok, float_type};
{X, Y} ->
case X =:= Y of
true ->
{ok, X};
false ->
{error, nil}
end
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 321).
-spec infer_cast(scope(), binary(), sqlode@query_ir:expr()) -> {ok,
inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
infer_cast(Scope, Target, _) ->
case sqlode@model:parse_sql_type(Target) of
{ok, Scalar} ->
ok(Scalar, true);
{error, _} ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<<<"unrecognised cast type \""/utf8, Target/binary>>/binary,
"\""/utf8>>}}
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 374).
-spec unify_fold(list(inferred_type()), sqlode@model:scalar_type(), boolean()) -> {ok,
{sqlode@model:scalar_type(), boolean()}} |
{error, nil}.
unify_fold(Items, Acc_type, Acc_nullable) ->
case Items of
[] ->
{ok, {Acc_type, Acc_nullable}};
[{inferred_type, T, N} | Rest] ->
case merge_numeric(Acc_type, T) of
{ok, Merged} ->
unify_fold(Rest, Merged, Acc_nullable orelse N);
{error, _} ->
case Acc_type =:= T of
true ->
unify_fold(Rest, Acc_type, Acc_nullable orelse N);
false ->
{error, nil}
end
end
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 365).
-spec unify_types(list(inferred_type())) -> {ok,
{sqlode@model:scalar_type(), boolean()}} |
{error, nil}.
unify_types(Items) ->
case Items of
[] ->
{error, nil};
[{inferred_type, T, N} | Rest] ->
unify_fold(Rest, T, N)
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 648).
-spec render_token(sqlode@lexer:token()) -> binary().
render_token(Token) ->
case Token of
{keyword, K} ->
K;
{ident, N} ->
N;
{quoted_ident, N@1} ->
<<<<"\""/utf8, N@1/binary>>/binary, "\""/utf8>>;
{string_lit, S} ->
<<<<"'"/utf8, S/binary>>/binary, "'"/utf8>>;
{number_lit, N@2} ->
N@2;
{placeholder, P} ->
P;
{operator, O} ->
O;
l_paren ->
<<"("/utf8>>;
r_paren ->
<<")"/utf8>>;
comma ->
<<","/utf8>>;
semicolon ->
<<";"/utf8>>;
dot ->
<<"."/utf8>>;
star ->
<<"*"/utf8>>
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 641).
-spec render_tokens(list(sqlode@lexer:token())) -> binary().
render_tokens(Tokens) ->
_pipe = Tokens,
_pipe@1 = gleam@list:map(_pipe, fun render_token/1),
_pipe@2 = gleam@list:filter(_pipe@1, fun(S) -> S /= <<""/utf8>> end),
gleam@string:join(_pipe@2, <<" "/utf8>>).
-file("src/sqlode/query_analyzer/type_inference.gleam", 206).
-spec infer_unary(scope(), binary(), sqlode@query_ir:expr()) -> {ok,
inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
infer_unary(Scope, Op, Arg) ->
case Op of
<<"not"/utf8>> ->
ok(bool_type, false);
<<"-"/utf8>> ->
infer_expr_type(Scope, Arg);
<<"+"/utf8>> ->
infer_expr_type(Scope, Arg);
_ ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"unary operator "/utf8, Op/binary>>}}
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 59).
-spec infer_expr_type(scope(), sqlode@query_ir:expr()) -> {ok, inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
infer_expr_type(Scope, Expr) ->
case Expr of
null_lit ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"NULL"/utf8>>}};
{bool_lit, _} ->
ok(bool_type, false);
{string_lit, _} ->
ok(string_type, false);
{number_lit, N} ->
ok(number_type(N), false);
{param, _, _} ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"parameter placeholder in result context requires a cast"/utf8>>}};
{column_ref, Table, Name} ->
resolve_column(Scope, Table, Name);
{star_ref, _} ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"* in expression context"/utf8>>}};
{unary, Op, Arg} ->
infer_unary(Scope, Op, Arg);
{binary, Op@1, Left, Right} ->
infer_binary(Scope, Op@1, Left, Right);
{func, _, _, _, _, _} = F ->
infer_function(Scope, F);
{cast, Inner, Target} ->
infer_cast(Scope, Target, Inner);
{'case', _, Branches, Else_} ->
infer_case(Scope, Branches, Else_);
{in_expr, _, _, _} ->
ok(bool_type, false);
{exists, _, _} ->
ok(bool_type, false);
{scalar_subquery, Core} ->
infer_scalar_subquery(Scope, Core);
{quantified, _, _, _, _} ->
ok(bool_type, false);
{between, _, _, _, _} ->
ok(bool_type, false);
{is_check, _, _, _} ->
ok(bool_type, false);
{like_expr, _, _, _, _, _} ->
ok(bool_type, false);
{array_lit, Elems} ->
case Elems of
[] ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"empty ARRAY[] literal"/utf8>>}};
[First | _] ->
case infer_expr_type(Scope, First) of
{ok, {inferred_type, T, _}} ->
ok({array_type, T}, false);
{error, E} ->
{error, E}
end
end;
{tuple, [Single]} ->
infer_expr_type(Scope, Single);
{tuple, _} ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"tuple expression"/utf8>>}};
{macro, Name@1, _} ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<<<"sqlode."/utf8, Name@1/binary>>/binary,
"(…) in this position"/utf8>>}};
{raw_expr, Reason, Tokens} ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<<<Reason/binary, ": "/utf8>>/binary,
(render_tokens(Tokens))/binary>>}}
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 272).
-spec infer_expr_type_allow_null(scope(), sqlode@query_ir:expr()) -> {ok,
inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
infer_expr_type_allow_null(Scope, Expr) ->
case Expr of
null_lit ->
ok_unknown_nullable();
{param, _, _} ->
ok_unknown();
{cast, _, T} ->
case sqlode@model:parse_sql_type(T) of
{ok, Scalar} ->
ok(Scalar, true);
{error, _} ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<<<"unrecognised cast type \""/utf8, T/binary>>/binary,
"\""/utf8>>}}
end;
_ ->
infer_expr_type(Scope, Expr)
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 222).
-spec infer_binary(
scope(),
binary(),
sqlode@query_ir:expr(),
sqlode@query_ir:expr()
) -> {ok, inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
infer_binary(Scope, Op, Left, Right) ->
case Op of
<<"and"/utf8>> ->
ok(bool_type, false);
<<"or"/utf8>> ->
ok(bool_type, false);
<<"="/utf8>> ->
ok(bool_type, false);
<<"<>"/utf8>> ->
ok(bool_type, false);
<<"!="/utf8>> ->
ok(bool_type, false);
<<"<"/utf8>> ->
ok(bool_type, false);
<<">"/utf8>> ->
ok(bool_type, false);
<<"<="/utf8>> ->
ok(bool_type, false);
<<">="/utf8>> ->
ok(bool_type, false);
<<"@>"/utf8>> ->
ok(bool_type, false);
<<"<@"/utf8>> ->
ok(bool_type, false);
<<"?|"/utf8>> ->
ok(bool_type, false);
<<"?&"/utf8>> ->
ok(bool_type, false);
<<"&&"/utf8>> ->
ok(bool_type, false);
<<"||"/utf8>> ->
gleam@result:'try'(
infer_expr_type_allow_null(Scope, Left),
fun(_) ->
gleam@result:'try'(
infer_expr_type_allow_null(Scope, Right),
fun(_) -> ok(string_type, false) end
)
end
);
<<"+"/utf8>> ->
gleam@result:'try'(
infer_expr_type_allow_null(Scope, Left),
fun(Lt) ->
gleam@result:'try'(
infer_expr_type_allow_null(Scope, Right),
fun(Rt) ->
case merge_numeric(
erlang:element(2, Lt),
erlang:element(2, Rt)
) of
{ok, Scalar} ->
ok(
Scalar,
erlang:element(3, Lt) orelse erlang:element(
3,
Rt
)
);
{error, _} ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"arithmetic operands have incompatible types"/utf8>>}}
end
end
)
end
);
<<"-"/utf8>> ->
gleam@result:'try'(
infer_expr_type_allow_null(Scope, Left),
fun(Lt) ->
gleam@result:'try'(
infer_expr_type_allow_null(Scope, Right),
fun(Rt) ->
case merge_numeric(
erlang:element(2, Lt),
erlang:element(2, Rt)
) of
{ok, Scalar} ->
ok(
Scalar,
erlang:element(3, Lt) orelse erlang:element(
3,
Rt
)
);
{error, _} ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"arithmetic operands have incompatible types"/utf8>>}}
end
end
)
end
);
<<"*"/utf8>> ->
gleam@result:'try'(
infer_expr_type_allow_null(Scope, Left),
fun(Lt) ->
gleam@result:'try'(
infer_expr_type_allow_null(Scope, Right),
fun(Rt) ->
case merge_numeric(
erlang:element(2, Lt),
erlang:element(2, Rt)
) of
{ok, Scalar} ->
ok(
Scalar,
erlang:element(3, Lt) orelse erlang:element(
3,
Rt
)
);
{error, _} ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"arithmetic operands have incompatible types"/utf8>>}}
end
end
)
end
);
<<"/"/utf8>> ->
gleam@result:'try'(
infer_expr_type_allow_null(Scope, Left),
fun(Lt) ->
gleam@result:'try'(
infer_expr_type_allow_null(Scope, Right),
fun(Rt) ->
case merge_numeric(
erlang:element(2, Lt),
erlang:element(2, Rt)
) of
{ok, Scalar} ->
ok(
Scalar,
erlang:element(3, Lt) orelse erlang:element(
3,
Rt
)
);
{error, _} ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"arithmetic operands have incompatible types"/utf8>>}}
end
end
)
end
);
<<"%"/utf8>> ->
gleam@result:'try'(
infer_expr_type_allow_null(Scope, Left),
fun(Lt) ->
gleam@result:'try'(
infer_expr_type_allow_null(Scope, Right),
fun(Rt) ->
case merge_numeric(
erlang:element(2, Lt),
erlang:element(2, Rt)
) of
{ok, Scalar} ->
ok(
Scalar,
erlang:element(3, Lt) orelse erlang:element(
3,
Rt
)
);
{error, _} ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"arithmetic operands have incompatible types"/utf8>>}}
end
end
)
end
);
<<"->"/utf8>> ->
ok(json_type, true);
<<"#>"/utf8>> ->
ok(json_type, true);
<<"->>"/utf8>> ->
ok(string_type, true);
<<"#>>"/utf8>> ->
ok(string_type, true);
_ ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"binary operator "/utf8, Op/binary>>}}
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 336).
-spec infer_case(
scope(),
list(sqlode@query_ir:case_branch()),
gleam@option:option(sqlode@query_ir:expr())
) -> {ok, inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
infer_case(Scope, Branches, Else_) ->
Branch_thens = gleam@list:map(Branches, fun(B) -> erlang:element(3, B) end),
Arms = case Else_ of
{some, E} ->
lists:append(Branch_thens, [E]);
none ->
Branch_thens
end,
gleam@result:'try'(
gleam@list:try_map(
Arms,
fun(Arm) -> infer_expr_type_allow_null(Scope, Arm) end
),
fun(Types) -> case unify_types(Types) of
{ok, {Scalar, Inner_nullable}} ->
Nullable = case Else_ of
{some, _} ->
Inner_nullable;
none ->
true
end,
ok(Scalar, Nullable);
{error, _} ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"CASE branches have incompatible types"/utf8>>}}
end end
).
-file("src/sqlode/query_analyzer/type_inference.gleam", 514).
-spec infer_first_arg(scope(), list(sqlode@query_ir:func_arg()), boolean()) -> {ok,
inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
infer_first_arg(Scope, Args, Nullable) ->
case Args of
[{func_arg, First} | _] ->
case infer_expr_type_allow_null(Scope, First) of
{ok, It} ->
ok(
erlang:element(2, It),
Nullable orelse erlang:element(3, It)
);
{error, E} ->
{error, E}
end;
[] ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"function call with no arguments"/utf8>>}}
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 533).
-spec infer_math_first_arg(scope(), list(sqlode@query_ir:func_arg())) -> {ok,
inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
infer_math_first_arg(Scope, Args) ->
case Args of
[] ->
ok(float_type, false);
[{func_arg, First} | _] ->
case infer_expr_type_allow_null(Scope, First) of
{ok, It} ->
ok(erlang:element(2, It), erlang:element(3, It));
{error, _} ->
ok(float_type, false)
end
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 547).
-spec infer_window_first(scope(), list(sqlode@query_ir:func_arg())) -> {ok,
inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
infer_window_first(Scope, Args) ->
case Args of
[{func_arg, First} | _] ->
case infer_expr_type_allow_null(Scope, First) of
{ok, It} ->
ok(erlang:element(2, It), true);
{error, E} ->
{error, E}
end;
[] ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"window function with no arguments"/utf8>>}}
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 565).
-spec infer_aggregate_from_first(
scope(),
list(sqlode@query_ir:func_arg()),
boolean()
) -> {ok, inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
infer_aggregate_from_first(Scope, Args, Nullable) ->
infer_first_arg(Scope, Args, Nullable).
-file("src/sqlode/query_analyzer/type_inference.gleam", 573).
-spec infer_coalesce(scope(), list(sqlode@query_ir:func_arg())) -> {ok,
inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
infer_coalesce(Scope, Args) ->
case Args of
[] ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"COALESCE requires at least one argument"/utf8>>}};
_ ->
gleam@result:'try'(
gleam@list:try_map(
Args,
fun(A) ->
{func_arg, E} = A,
infer_expr_type_allow_null(Scope, E)
end
),
fun(Types) -> case unify_types(Types) of
{ok, {Scalar, _}} ->
Any_non_null = gleam@list:any(
Types,
fun(T) ->
{inferred_type, _, N} = T,
not N
end
),
ok(Scalar, not Any_non_null);
{error, _} ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"COALESCE arguments have incompatible types"/utf8>>}}
end end
)
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 418).
-spec infer_function_body(
scope(),
binary(),
list(sqlode@query_ir:func_arg()),
boolean()
) -> {ok, inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
infer_function_body(Scope, Name, Args, Window) ->
case Name of
<<"count"/utf8>> ->
ok(int_type, false);
<<"sum"/utf8>> ->
infer_aggregate_from_first(Scope, Args, true);
<<"min"/utf8>> ->
infer_aggregate_from_first(Scope, Args, true);
<<"max"/utf8>> ->
infer_aggregate_from_first(Scope, Args, true);
<<"avg"/utf8>> ->
ok(float_type, true);
<<"row_number"/utf8>> ->
ok(int_type, false);
<<"rank"/utf8>> ->
ok(int_type, false);
<<"dense_rank"/utf8>> ->
ok(int_type, false);
<<"ntile"/utf8>> ->
ok(int_type, false);
<<"percent_rank"/utf8>> ->
ok(float_type, false);
<<"cume_dist"/utf8>> ->
ok(float_type, false);
<<"lag"/utf8>> ->
infer_window_first(Scope, Args);
<<"lead"/utf8>> ->
infer_window_first(Scope, Args);
<<"first_value"/utf8>> ->
infer_window_first(Scope, Args);
<<"last_value"/utf8>> ->
infer_window_first(Scope, Args);
<<"nth_value"/utf8>> ->
infer_window_first(Scope, Args);
<<"coalesce"/utf8>> ->
infer_coalesce(Scope, Args);
<<"greatest"/utf8>> ->
infer_aggregate_from_first(Scope, Args, true);
<<"least"/utf8>> ->
infer_aggregate_from_first(Scope, Args, true);
<<"nullif"/utf8>> ->
infer_first_arg(Scope, Args, true);
<<"ifnull"/utf8>> ->
infer_first_arg(Scope, Args, true);
<<"nvl"/utf8>> ->
infer_first_arg(Scope, Args, true);
<<"abs"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"round"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"floor"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"ceil"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"ceiling"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"mod"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"power"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"sqrt"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"sign"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"trunc"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"log"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"ln"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"exp"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"random"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"pi"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"degrees"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"radians"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"div"/utf8>> ->
infer_math_first_arg(Scope, Args);
<<"length"/utf8>> ->
ok(int_type, false);
<<"char_length"/utf8>> ->
ok(int_type, false);
<<"character_length"/utf8>> ->
ok(int_type, false);
<<"octet_length"/utf8>> ->
ok(int_type, false);
<<"bit_length"/utf8>> ->
ok(int_type, false);
<<"position"/utf8>> ->
ok(int_type, false);
<<"strpos"/utf8>> ->
ok(int_type, false);
<<"ascii"/utf8>> ->
ok(int_type, false);
<<"replace"/utf8>> ->
ok(string_type, false);
<<"lower"/utf8>> ->
ok(string_type, false);
<<"upper"/utf8>> ->
ok(string_type, false);
<<"trim"/utf8>> ->
ok(string_type, false);
<<"ltrim"/utf8>> ->
ok(string_type, false);
<<"rtrim"/utf8>> ->
ok(string_type, false);
<<"substr"/utf8>> ->
ok(string_type, false);
<<"substring"/utf8>> ->
ok(string_type, false);
<<"concat"/utf8>> ->
ok(string_type, false);
<<"reverse"/utf8>> ->
ok(string_type, false);
<<"lpad"/utf8>> ->
ok(string_type, false);
<<"rpad"/utf8>> ->
ok(string_type, false);
<<"left"/utf8>> ->
ok(string_type, false);
<<"right"/utf8>> ->
ok(string_type, false);
<<"repeat"/utf8>> ->
ok(string_type, false);
<<"initcap"/utf8>> ->
ok(string_type, false);
<<"translate"/utf8>> ->
ok(string_type, false);
<<"to_char"/utf8>> ->
ok(string_type, false);
<<"format"/utf8>> ->
ok(string_type, false);
<<"quote_literal"/utf8>> ->
ok(string_type, false);
<<"quote_ident"/utf8>> ->
ok(string_type, false);
<<"md5"/utf8>> ->
ok(string_type, false);
<<"encode"/utf8>> ->
ok(string_type, false);
<<"decode"/utf8>> ->
ok(string_type, false);
<<"now"/utf8>> ->
ok(date_time_type, false);
<<"current_timestamp"/utf8>> ->
ok(date_time_type, false);
<<"clock_timestamp"/utf8>> ->
ok(date_time_type, false);
<<"statement_timestamp"/utf8>> ->
ok(date_time_type, false);
<<"timeofday"/utf8>> ->
ok(date_time_type, false);
<<"localtimestamp"/utf8>> ->
ok(date_time_type, false);
<<"current_date"/utf8>> ->
ok(date_type, false);
<<"current_time"/utf8>> ->
ok(time_type, false);
<<"localtime"/utf8>> ->
ok(time_type, false);
<<"make_date"/utf8>> ->
ok(date_type, false);
<<"to_date"/utf8>> ->
ok(date_type, false);
<<"make_time"/utf8>> ->
ok(time_type, false);
<<"make_timestamp"/utf8>> ->
ok(date_time_type, false);
<<"to_timestamp"/utf8>> ->
ok(date_time_type, false);
<<"date_trunc"/utf8>> ->
ok(date_time_type, false);
<<"age"/utf8>> ->
ok(date_time_type, false);
<<"date_part"/utf8>> ->
ok(float_type, false);
<<"extract"/utf8>> ->
ok(float_type, false);
<<"date"/utf8>> ->
ok(date_type, false);
<<"time"/utf8>> ->
ok(time_type, false);
<<"timestamp"/utf8>> ->
ok(date_time_type, false);
<<"interval"/utf8>> ->
ok(time_type, false);
_ ->
case Window of
true ->
ok(int_type, false);
false ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"unknown function "/utf8, Name/binary>>}}
end
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 398).
-spec infer_function(scope(), sqlode@query_ir:expr()) -> {ok, inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
infer_function(Scope, F) ->
case F of
{func, Name, Args, _, _, Over} ->
Window = case Over of
{some, _} ->
true;
none ->
false
end,
infer_function_body(Scope, Name, Args, Window);
_ ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"internal: infer_function called with non-Func"/utf8>>}}
end.
-file("src/sqlode/query_analyzer/type_inference.gleam", 614).
-spec infer_scalar_subquery(scope(), sqlode@query_ir:select_core()) -> {ok,
inferred_type()} |
{error, sqlode@query_analyzer@context:analysis_error()}.
infer_scalar_subquery(Scope, Core) ->
case erlang:element(3, Core) of
[{expr_item, Expr, _} | _] ->
case infer_expr_type(Scope, Expr) of
{ok, {inferred_type, T, _}} ->
ok(T, true);
{error, E} ->
{error, E}
end;
[{star_ex, _} | _] ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"scalar subquery returning *"/utf8>>}};
[] ->
{error,
{unsupported_expression,
erlang:element(2, Scope),
<<"scalar subquery has no select items"/utf8>>}}
end.