Current section

Files

Jump to
sqlode src sqlode@model.erl
Raw

src/sqlode@model.erl

-module(sqlode@model).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/sqlode/model.gleam").
-export([parse_engine/1, engine_to_string/1, parse_type_mapping/1, type_mapping_to_string/1, parse_runtime/1, runtime_to_string/1, empty_overrides/0, is_result_command/1, parse_query_command/1, query_command_to_string/1, parse_sql_type/1, parse_sql_type_for_engine/2]).
-export_type([engine/0, runtime/0, type_mapping/0, type_override/0, column_rename/0, overrides/0, gleam_output/0, sql_block/0, config/0, macro/0, parsed_query/0, scalar_type/0, enum_kind/0, enum_def/0, normalized_type/0, column/0, table/0, catalog/0, query_param/0, result_column/0, embedded_column/0, result_item/0, analyzed_query/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.
-type engine() :: postgre_s_q_l | my_s_q_l | s_q_lite.
-type runtime() :: raw | native.
-type type_mapping() :: string_mapping | rich_mapping | strong_mapping.
-type type_override() :: {db_type_override,
binary(),
binary(),
gleam@option:option(boolean())} |
{column_override, binary(), binary(), binary()}.
-type column_rename() :: {column_rename, binary(), binary(), binary()}.
-type overrides() :: {overrides, list(type_override()), list(column_rename())}.
-type gleam_output() :: {gleam_output,
binary(),
runtime(),
type_mapping(),
boolean(),
boolean(),
boolean(),
boolean(),
boolean(),
gleam@option:option(integer())}.
-type sql_block() :: {sql_block,
gleam@option:option(binary()),
engine(),
list(binary()),
list(binary()),
gleam_output(),
overrides()}.
-type config() :: {config, integer(), list(sql_block())}.
-type macro() :: {macro_arg, integer(), binary()} |
{macro_narg, integer(), binary()} |
{macro_slice, integer(), binary()}.
-type parsed_query() :: {parsed_query,
binary(),
binary(),
sqlode@runtime:query_command(),
binary(),
binary(),
integer(),
list(macro())}.
-type scalar_type() :: int_type |
float_type |
bool_type |
string_type |
bytes_type |
date_time_type |
date_type |
time_type |
uuid_type |
json_type |
decimal_type |
{enum_type, binary()} |
{set_type, binary()} |
{custom_type, binary(), gleam@option:option(binary()), scalar_type()} |
{array_type, scalar_type()}.
-type enum_kind() :: postgres_enum | my_sql_enum | my_sql_set.
-type enum_def() :: {enum_def, binary(), list(binary()), enum_kind()}.
-type normalized_type() :: {normalized_type, binary(), boolean()}.
-type column() :: {column, binary(), scalar_type(), boolean()}.
-type table() :: {table, binary(), list(column())}.
-type catalog() :: {catalog, list(table()), list(enum_def())}.
-type query_param() :: {query_param,
integer(),
binary(),
scalar_type(),
boolean(),
boolean()}.
-type result_column() :: {result_column,
binary(),
scalar_type(),
boolean(),
gleam@option:option(binary())}.
-type embedded_column() :: {embedded_column, binary(), binary(), list(column())}.
-type result_item() :: {scalar_result, result_column()} |
{embedded_result, embedded_column()}.
-type analyzed_query() :: {analyzed_query,
parsed_query(),
list(query_param()),
list(result_item())}.
-file("src/sqlode/model.gleam", 12).
-spec parse_engine(binary()) -> {ok, engine()} | {error, binary()}.
parse_engine(Value) ->
case Value of
<<"postgresql"/utf8>> ->
{ok, postgre_s_q_l};
<<"mysql"/utf8>> ->
{ok, my_s_q_l};
<<"sqlite"/utf8>> ->
{ok, s_q_lite};
_ ->
{error, <<"must be one of: postgresql, mysql, sqlite"/utf8>>}
end.
-file("src/sqlode/model.gleam", 21).
-spec engine_to_string(engine()) -> binary().
engine_to_string(Engine) ->
case Engine of
postgre_s_q_l ->
<<"postgresql"/utf8>>;
my_s_q_l ->
<<"mysql"/utf8>>;
s_q_lite ->
<<"sqlite"/utf8>>
end.
-file("src/sqlode/model.gleam", 40).
-spec parse_type_mapping(binary()) -> {ok, type_mapping()} | {error, binary()}.
parse_type_mapping(Value) ->
case Value of
<<"string"/utf8>> ->
{ok, string_mapping};
<<"rich"/utf8>> ->
{ok, rich_mapping};
<<"strong"/utf8>> ->
{ok, strong_mapping};
_ ->
{error, <<"must be one of: string, rich, strong"/utf8>>}
end.
-file("src/sqlode/model.gleam", 49).
-spec type_mapping_to_string(type_mapping()) -> binary().
type_mapping_to_string(Mapping) ->
case Mapping of
string_mapping ->
<<"string"/utf8>>;
rich_mapping ->
<<"rich"/utf8>>;
strong_mapping ->
<<"strong"/utf8>>
end.
-file("src/sqlode/model.gleam", 57).
-spec parse_runtime(binary()) -> {ok, runtime()} | {error, binary()}.
parse_runtime(Value) ->
case Value of
<<"raw"/utf8>> ->
{ok, raw};
<<"based"/utf8>> ->
{error,
<<"\"based\" is not yet supported; use \"raw\" or \"native\" instead"/utf8>>};
<<"native"/utf8>> ->
{ok, native};
_ ->
{error, <<"must be one of: raw, native"/utf8>>}
end.
-file("src/sqlode/model.gleam", 67).
-spec runtime_to_string(runtime()) -> binary().
runtime_to_string(Runtime) ->
case Runtime of
raw ->
<<"raw"/utf8>>;
native ->
<<"native"/utf8>>
end.
-file("src/sqlode/model.gleam", 90).
-spec empty_overrides() -> overrides().
empty_overrides() ->
{overrides, [], []}.
-file("src/sqlode/model.gleam", 144).
-spec is_result_command(sqlode@runtime:query_command()) -> boolean().
is_result_command(Command) ->
case Command of
query_one ->
true;
query_many ->
true;
query_batch_one ->
true;
query_batch_many ->
true;
_ ->
false
end.
-file("src/sqlode/model.gleam", 154).
-spec parse_query_command(binary()) -> {ok, sqlode@runtime:query_command()} |
{error, binary()}.
parse_query_command(Value) ->
case Value of
<<":one"/utf8>> ->
{ok, query_one};
<<":many"/utf8>> ->
{ok, query_many};
<<":exec"/utf8>> ->
{ok, query_exec};
<<":execresult"/utf8>> ->
{ok, query_exec_result};
<<":execrows"/utf8>> ->
{ok, query_exec_rows};
<<":execlastid"/utf8>> ->
{ok, query_exec_last_id};
<<":batchone"/utf8>> ->
{ok, query_batch_one};
<<":batchmany"/utf8>> ->
{ok, query_batch_many};
<<":batchexec"/utf8>> ->
{ok, query_batch_exec};
<<":copyfrom"/utf8>> ->
{ok, query_copy_from};
_ ->
{error,
<<"must be one of: :one, :many, :exec, :execresult, :execrows, :execlastid, :batchone, :batchmany, :batchexec, :copyfrom"/utf8>>}
end.
-file("src/sqlode/model.gleam", 175).
-spec query_command_to_string(sqlode@runtime:query_command()) -> binary().
query_command_to_string(Command) ->
case Command of
query_one ->
<<"QueryOne"/utf8>>;
query_many ->
<<"QueryMany"/utf8>>;
query_exec ->
<<"QueryExec"/utf8>>;
query_exec_result ->
<<"QueryExecResult"/utf8>>;
query_exec_rows ->
<<"QueryExecRows"/utf8>>;
query_exec_last_id ->
<<"QueryExecLastId"/utf8>>;
query_batch_one ->
<<"QueryBatchOne"/utf8>>;
query_batch_many ->
<<"QueryBatchMany"/utf8>>;
query_batch_exec ->
<<"QueryBatchExec"/utf8>>;
query_copy_from ->
<<"QueryCopyFrom"/utf8>>
end.
-file("src/sqlode/model.gleam", 321).
-spec strip_mysql_signedness(binary()) -> binary().
strip_mysql_signedness(Text) ->
_pipe = Text,
_pipe@1 = gleam@string:replace(
_pipe,
<<" unsigned zerofill"/utf8>>,
<<""/utf8>>
),
_pipe@2 = gleam@string:replace(
_pipe@1,
<<" zerofill unsigned"/utf8>>,
<<""/utf8>>
),
_pipe@3 = gleam@string:replace(_pipe@2, <<" unsigned"/utf8>>, <<""/utf8>>),
_pipe@4 = gleam@string:replace(_pipe@3, <<" signed"/utf8>>, <<""/utf8>>),
_pipe@5 = gleam@string:replace(_pipe@4, <<" zerofill"/utf8>>, <<""/utf8>>),
gleam@string:trim(_pipe@5).
-file("src/sqlode/model.gleam", 331).
-spec detect_tinyint_one(binary()) -> boolean().
detect_tinyint_one(Text) ->
case gleam@string:split_once(Text, <<"("/utf8>>) of
{ok, {Head, Rest}} ->
case {gleam@string:trim(Head) =:= <<"tinyint"/utf8>>,
gleam@string:split_once(Rest, <<")"/utf8>>)} of
{true, {ok, {Arg, _}}} ->
gleam@string:trim(Arg) =:= <<"1"/utf8>>;
{_, _} ->
false
end;
{error, nil} ->
false
end.
-file("src/sqlode/model.gleam", 342).
-spec is_mysql_decimal_text(binary()) -> boolean().
is_mysql_decimal_text(Text) ->
Base = case gleam@string:split_once(Text, <<"("/utf8>>) of
{ok, {Head, _}} ->
gleam@string:trim(Head);
{error, nil} ->
gleam@string:trim(Text)
end,
(((Base =:= <<"decimal"/utf8>>) orelse (Base =:= <<"numeric"/utf8>>)) orelse (Base
=:= <<"dec"/utf8>>))
orelse (Base =:= <<"fixed"/utf8>>).
-file("src/sqlode/model.gleam", 383).
-spec strip_array_suffix(binary(), boolean()) -> {binary(), boolean()}.
strip_array_suffix(Text, Seen) ->
Trimmed = gleam@string:trim_end(Text),
case gleam_stdlib:string_ends_with(Trimmed, <<"[]"/utf8>>) of
true ->
strip_array_suffix(gleam@string:drop_end(Trimmed, 2), true);
false ->
case gleam_stdlib:string_ends_with(Trimmed, <<" array"/utf8>>) of
true ->
strip_array_suffix(gleam@string:drop_end(Trimmed, 6), true);
false ->
{Trimmed, Seen}
end
end.
-file("src/sqlode/model.gleam", 395).
-spec strip_modifier(binary()) -> binary().
strip_modifier(Text) ->
After_open = case gleam@string:split_once(Text, <<"("/utf8>>) of
{ok, {Head, _}} ->
Head;
{error, nil} ->
Text
end,
case gleam@string:split_once(After_open, <<")"/utf8>>) of
{ok, {Head@1, _}} ->
Head@1;
{error, nil} ->
After_open
end.
-file("src/sqlode/model.gleam", 410).
-spec collapse_whitespace(binary()) -> binary().
collapse_whitespace(Text) ->
_pipe = Text,
_pipe@1 = gleam@string:split(_pipe, <<" "/utf8>>),
_pipe@2 = gleam@list:filter(_pipe@1, fun(Part) -> Part /= <<""/utf8>> end),
gleam@string:join(_pipe@2, <<" "/utf8>>).
-file("src/sqlode/model.gleam", 369).
-spec normalize_type_text(binary()) -> normalized_type().
normalize_type_text(Type_text) ->
Lowered = begin
_pipe = Type_text,
_pipe@1 = string:lowercase(_pipe),
gleam@string:trim(_pipe@1)
end,
{Without_array, Is_array} = strip_array_suffix(Lowered, false),
Without_modifier = strip_modifier(Without_array),
Base = begin
_pipe@2 = Without_modifier,
_pipe@3 = collapse_whitespace(_pipe@2),
gleam@string:trim(_pipe@3)
end,
{normalized_type, Base, Is_array}.
-file("src/sqlode/model.gleam", 417).
-spec classify_builtin_type(binary()) -> {ok, scalar_type()} | {error, nil}.
classify_builtin_type(Base) ->
case Base of
<<"int"/utf8>> ->
{ok, int_type};
<<"int2"/utf8>> ->
{ok, int_type};
<<"int4"/utf8>> ->
{ok, int_type};
<<"int8"/utf8>> ->
{ok, int_type};
<<"integer"/utf8>> ->
{ok, int_type};
<<"smallint"/utf8>> ->
{ok, int_type};
<<"bigint"/utf8>> ->
{ok, int_type};
<<"mediumint"/utf8>> ->
{ok, int_type};
<<"tinyint"/utf8>> ->
{ok, int_type};
<<"serial"/utf8>> ->
{ok, int_type};
<<"serial2"/utf8>> ->
{ok, int_type};
<<"serial4"/utf8>> ->
{ok, int_type};
<<"serial8"/utf8>> ->
{ok, int_type};
<<"smallserial"/utf8>> ->
{ok, int_type};
<<"bigserial"/utf8>> ->
{ok, int_type};
<<"year"/utf8>> ->
{ok, int_type};
<<"float"/utf8>> ->
{ok, float_type};
<<"float4"/utf8>> ->
{ok, float_type};
<<"float8"/utf8>> ->
{ok, float_type};
<<"real"/utf8>> ->
{ok, float_type};
<<"double"/utf8>> ->
{ok, float_type};
<<"double precision"/utf8>> ->
{ok, float_type};
<<"numeric"/utf8>> ->
{ok, float_type};
<<"decimal"/utf8>> ->
{ok, float_type};
<<"dec"/utf8>> ->
{ok, float_type};
<<"money"/utf8>> ->
{ok, float_type};
<<"smallmoney"/utf8>> ->
{ok, float_type};
<<"bool"/utf8>> ->
{ok, bool_type};
<<"boolean"/utf8>> ->
{ok, bool_type};
<<"bytea"/utf8>> ->
{ok, bytes_type};
<<"blob"/utf8>> ->
{ok, bytes_type};
<<"longblob"/utf8>> ->
{ok, bytes_type};
<<"mediumblob"/utf8>> ->
{ok, bytes_type};
<<"tinyblob"/utf8>> ->
{ok, bytes_type};
<<"binary"/utf8>> ->
{ok, bytes_type};
<<"varbinary"/utf8>> ->
{ok, bytes_type};
<<"uuid"/utf8>> ->
{ok, uuid_type};
<<"uniqueidentifier"/utf8>> ->
{ok, uuid_type};
<<"json"/utf8>> ->
{ok, json_type};
<<"jsonb"/utf8>> ->
{ok, json_type};
<<"timestamp"/utf8>> ->
{ok, date_time_type};
<<"timestamptz"/utf8>> ->
{ok, date_time_type};
<<"timestamp with time zone"/utf8>> ->
{ok, date_time_type};
<<"timestamp without time zone"/utf8>> ->
{ok, date_time_type};
<<"datetime"/utf8>> ->
{ok, date_time_type};
<<"datetime2"/utf8>> ->
{ok, date_time_type};
<<"date"/utf8>> ->
{ok, date_type};
<<"time"/utf8>> ->
{ok, time_type};
<<"timetz"/utf8>> ->
{ok, time_type};
<<"time with time zone"/utf8>> ->
{ok, time_type};
<<"time without time zone"/utf8>> ->
{ok, time_type};
<<"interval"/utf8>> ->
{ok, time_type};
<<"text"/utf8>> ->
{ok, string_type};
<<"char"/utf8>> ->
{ok, string_type};
<<"character"/utf8>> ->
{ok, string_type};
<<"character varying"/utf8>> ->
{ok, string_type};
<<"varchar"/utf8>> ->
{ok, string_type};
<<"bpchar"/utf8>> ->
{ok, string_type};
<<"nchar"/utf8>> ->
{ok, string_type};
<<"nvarchar"/utf8>> ->
{ok, string_type};
<<"clob"/utf8>> ->
{ok, string_type};
<<"nclob"/utf8>> ->
{ok, string_type};
<<"longtext"/utf8>> ->
{ok, string_type};
<<"mediumtext"/utf8>> ->
{ok, string_type};
<<"tinytext"/utf8>> ->
{ok, string_type};
<<"string"/utf8>> ->
{ok, string_type};
<<"name"/utf8>> ->
{ok, string_type};
<<"citext"/utf8>> ->
{ok, string_type};
<<"inet"/utf8>> ->
{ok, string_type};
<<"cidr"/utf8>> ->
{ok, string_type};
<<"macaddr"/utf8>> ->
{ok, string_type};
<<"macaddr8"/utf8>> ->
{ok, string_type};
<<"tsvector"/utf8>> ->
{ok, string_type};
<<"tsquery"/utf8>> ->
{ok, string_type};
<<"point"/utf8>> ->
{ok, string_type};
<<"line"/utf8>> ->
{ok, string_type};
<<"lseg"/utf8>> ->
{ok, string_type};
<<"box"/utf8>> ->
{ok, string_type};
<<"path"/utf8>> ->
{ok, string_type};
<<"polygon"/utf8>> ->
{ok, string_type};
<<"circle"/utf8>> ->
{ok, string_type};
<<"xml"/utf8>> ->
{ok, string_type};
<<"bit"/utf8>> ->
{ok, string_type};
<<"bit varying"/utf8>> ->
{ok, string_type};
<<"varbit"/utf8>> ->
{ok, string_type};
_ ->
{error, nil}
end.
-file("src/sqlode/model.gleam", 354).
?DOC(
" Classify by exact compound name first (e.g. \"timestamp with time zone\"),\n"
" then fall back to the first whitespace-separated token so trailing\n"
" garbage from unsupported column clauses (GENERATED, PARTITION BY) does\n"
" not block recognition of the primary type keyword.\n"
).
-spec classify_normalized_base(binary()) -> {ok, scalar_type()} | {error, nil}.
classify_normalized_base(Base) ->
case classify_builtin_type(Base) of
{ok, T} ->
{ok, T};
{error, nil} ->
case gleam@string:split_once(Base, <<" "/utf8>>) of
{ok, {First_word, _}} ->
classify_builtin_type(First_word);
{error, nil} ->
{error, nil}
end
end.
-file("src/sqlode/model.gleam", 268).
?DOC(
" Parse a SQL type name into a ScalarType by normalizing the type token\n"
" (lowercasing, stripping modifiers and array markers, collapsing whitespace)\n"
" and matching the base name against a fixed table of built-ins.\n"
"\n"
" Used by both schema parsing (CREATE TABLE column types) and query analysis\n"
" (PostgreSQL type casts like `$1::int`). Returns Error(Nil) for unrecognized\n"
" types so callers can surface the original text in a precise diagnostic\n"
" instead of guessing.\n"
).
-spec parse_sql_type(binary()) -> {ok, scalar_type()} | {error, nil}.
parse_sql_type(Type_text) ->
Normalized = normalize_type_text(Type_text),
case classify_normalized_base(erlang:element(2, Normalized)) of
{ok, Element_type} ->
case erlang:element(3, Normalized) of
true ->
{ok, {array_type, Element_type}};
false ->
{ok, Element_type}
end;
{error, nil} ->
{error, nil}
end.
-file("src/sqlode/model.gleam", 305).
-spec classify_mysql_type_text(binary()) -> {ok, scalar_type()} | {error, nil}.
classify_mysql_type_text(Type_text) ->
Lowered = begin
_pipe = Type_text,
_pipe@1 = string:lowercase(_pipe),
gleam@string:trim(_pipe@1)
end,
Stripped = strip_mysql_signedness(Lowered),
case detect_tinyint_one(Stripped) of
true ->
{ok, bool_type};
false ->
case is_mysql_decimal_text(Stripped) of
true ->
{ok, decimal_type};
false ->
parse_sql_type(Stripped)
end
end.
-file("src/sqlode/model.gleam", 291).
?DOC(
" Engine-aware variant of `parse_sql_type`. Used when the column type\n"
" classification depends on more than the first keyword:\n"
"\n"
" * MySQL `TINYINT(1)` (and only the (1) variant) is a Boolean by\n"
" convention; bare `TINYINT` and `TINYINT(N>1)` stay `Int`.\n"
" * MySQL `UNSIGNED` / `SIGNED` suffixes are noise — strip them\n"
" before classification so `BIGINT UNSIGNED` still resolves to\n"
" `Int`.\n"
" * MySQL `DECIMAL(p,s)` / `NUMERIC(p,s)` / `DEC(p,s)` resolve to\n"
" `DecimalType` (lossless `String`) instead of being silently\n"
" collapsed to `Float` like for other engines.\n"
).
-spec parse_sql_type_for_engine(binary(), engine()) -> {ok, scalar_type()} |
{error, nil}.
parse_sql_type_for_engine(Type_text, Engine) ->
case Engine of
my_s_q_l ->
case classify_mysql_type_text(Type_text) of
{ok, T} ->
{ok, T};
{error, nil} ->
parse_sql_type(Type_text)
end;
postgre_s_q_l ->
parse_sql_type(Type_text);
s_q_lite ->
parse_sql_type(Type_text)
end.