-module(sqlode@internal@query_analyzer@type_inference). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/sqlode/internal/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(false). -type inferred_type() :: {inferred_type, sqlode@internal@model:scalar_type(), boolean()}. -type scope() :: {scope, binary(), sqlode@internal@model:catalog(), list(binary()), list(binary())}. -file("src/sqlode/internal/query_analyzer/type_inference.gleam", 47). ?DOC(false). -spec scope( binary(), sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 134). ?DOC(false). -spec ok(sqlode@internal@model:scalar_type(), boolean()) -> {ok, inferred_type()} | {error, sqlode@internal@query_analyzer@context:analysis_error()}. ok(Scalar, Nullable) -> {ok, {inferred_type, Scalar, Nullable}}. -file("src/sqlode/internal/query_analyzer/type_inference.gleam", 141). ?DOC(false). -spec number_type(binary()) -> sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 156). ?DOC(false). -spec resolve_column(scope(), gleam@option:option(binary()), binary()) -> {ok, inferred_type()} | {error, sqlode@internal@query_analyzer@context:analysis_error()}. resolve_column(Scope, Table, Name) -> case Table of {some, T} -> case sqlode@internal@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@internal@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/internal/query_analyzer/type_inference.gleam", 294). ?DOC(false). -spec ok_unknown() -> {ok, inferred_type()} | {error, sqlode@internal@query_analyzer@context:analysis_error()}. ok_unknown() -> {ok, {inferred_type, int_type, false}}. -file("src/sqlode/internal/query_analyzer/type_inference.gleam", 298). ?DOC(false). -spec ok_unknown_nullable() -> {ok, inferred_type()} | {error, sqlode@internal@query_analyzer@context:analysis_error()}. ok_unknown_nullable() -> {ok, {inferred_type, int_type, true}}. -file("src/sqlode/internal/query_analyzer/type_inference.gleam", 302). ?DOC(false). -spec merge_numeric( sqlode@internal@model:scalar_type(), sqlode@internal@model:scalar_type() ) -> {ok, sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 323). ?DOC(false). -spec infer_cast(scope(), binary(), sqlode@internal@query_ir:expr()) -> {ok, inferred_type()} | {error, sqlode@internal@query_analyzer@context:analysis_error()}. infer_cast(Scope, Target, _) -> case sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 376). ?DOC(false). -spec unify_fold( list(inferred_type()), sqlode@internal@model:scalar_type(), boolean() ) -> {ok, {sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 367). ?DOC(false). -spec unify_types(list(inferred_type())) -> {ok, {sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 650). ?DOC(false). -spec render_token(sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 643). ?DOC(false). -spec render_tokens(list(sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 208). ?DOC(false). -spec infer_unary(scope(), binary(), sqlode@internal@query_ir:expr()) -> {ok, inferred_type()} | {error, sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 61). ?DOC(false). -spec infer_expr_type(scope(), sqlode@internal@query_ir:expr()) -> {ok, inferred_type()} | {error, sqlode@internal@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), <<<>/binary, (render_tokens(Tokens))/binary>>}} end. -file("src/sqlode/internal/query_analyzer/type_inference.gleam", 274). ?DOC(false). -spec infer_expr_type_allow_null(scope(), sqlode@internal@query_ir:expr()) -> {ok, inferred_type()} | {error, sqlode@internal@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@internal@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/internal/query_analyzer/type_inference.gleam", 224). ?DOC(false). -spec infer_binary( scope(), binary(), sqlode@internal@query_ir:expr(), sqlode@internal@query_ir:expr() ) -> {ok, inferred_type()} | {error, sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 338). ?DOC(false). -spec infer_case( scope(), list(sqlode@internal@query_ir:case_branch()), gleam@option:option(sqlode@internal@query_ir:expr()) ) -> {ok, inferred_type()} | {error, sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 516). ?DOC(false). -spec infer_first_arg( scope(), list(sqlode@internal@query_ir:func_arg()), boolean() ) -> {ok, inferred_type()} | {error, sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 535). ?DOC(false). -spec infer_math_first_arg(scope(), list(sqlode@internal@query_ir:func_arg())) -> {ok, inferred_type()} | {error, sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 549). ?DOC(false). -spec infer_window_first(scope(), list(sqlode@internal@query_ir:func_arg())) -> {ok, inferred_type()} | {error, sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 567). ?DOC(false). -spec infer_aggregate_from_first( scope(), list(sqlode@internal@query_ir:func_arg()), boolean() ) -> {ok, inferred_type()} | {error, sqlode@internal@query_analyzer@context:analysis_error()}. infer_aggregate_from_first(Scope, Args, Nullable) -> infer_first_arg(Scope, Args, Nullable). -file("src/sqlode/internal/query_analyzer/type_inference.gleam", 575). ?DOC(false). -spec infer_coalesce(scope(), list(sqlode@internal@query_ir:func_arg())) -> {ok, inferred_type()} | {error, sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 420). ?DOC(false). -spec infer_function_body( scope(), binary(), list(sqlode@internal@query_ir:func_arg()), boolean() ) -> {ok, inferred_type()} | {error, sqlode@internal@query_analyzer@context:analysis_error()}. infer_function_body(Scope, Name, Args, Window) -> case Name of <<"count"/utf8>> -> ok(int_type, false); <<"last_insert_rowid"/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/internal/query_analyzer/type_inference.gleam", 400). ?DOC(false). -spec infer_function(scope(), sqlode@internal@query_ir:expr()) -> {ok, inferred_type()} | {error, sqlode@internal@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/internal/query_analyzer/type_inference.gleam", 616). ?DOC(false). -spec infer_scalar_subquery(scope(), sqlode@internal@query_ir:select_core()) -> {ok, inferred_type()} | {error, sqlode@internal@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.