-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), <<<>/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.