%% @author Maas-Maarten Zeeman %% @copyright 2022-2024 Maas-Maarten Zeeman %% %% @doc Low level erlang API for duckdb databases. %% @end %% Copyright 2022-2024 Maas-Maarten Zeeman %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -module(educkdb). -author("Maas-Maarten Zeeman "). %% low-level exports -export([ open/1, open/2, close/1, config_flag_info/0, connect/1, disconnect/1, query/2, prepare/2, statement_type/1, execute_prepared/1, parameter_name/2, parameter_type/2, parameter_count/1, parameter_index/2, clear_bindings/1, bind_boolean/3, bind_int8/3, bind_int16/3, bind_int32/3, bind_int64/3, bind_uint8/3, bind_uint16/3, bind_uint32/3, bind_uint64/3, bind_float/3, bind_double/3, bind_date/3, bind_time/3, bind_timestamp/3, bind_varchar/3, bind_null/2, %% Results result_extract/1, fetch_chunk/1, get_chunks/1, column_names/1, %% Chunks chunk_column_count/1, chunk_column_types/1, chunk_columns/1, chunk_size/1, appender_create/3, append_boolean/2, append_int8/2, append_int16/2, append_int32/2, append_int64/2, append_uint8/2, append_uint16/2, append_uint32/2, append_uint64/2, append_float/2, append_double/2, append_time/2, append_date/2, append_timestamp/2, append_varchar/2, append_null/1, appender_flush/1, appender_end_row/1 ]). %% High Level Api -export([ squery/2, execute/1 ]). %% Utilities -export([ uuid_binary_to_uuid_string/1, uuid_string_to_uuid_binary/1, hugeint_to_integer/1, integer_to_hugeint/1, uhugeint_to_integer/1, integer_to_uhugeint/1 ]). -include("educkdb.hrl"). -type database() :: reference(). -type connection() :: reference(). -type prepared_statement() :: reference(). -type result() :: reference(). -type appender() :: reference(). -type data_chunk() :: reference(). -type sql() :: iodata(). -type idx() :: uint64(). % DuckDB index type -type int8() :: -16#7F..16#7F. % DuckDB tinyint -type uint8() :: 0..16#FF. % DuckDB duckdb utinyint -type int16() :: -16#7FFF..16#7FFF. % DuckDB shortint -type uint16() :: 0..16#FFFF. % DuckDB ushortint -type int32() :: -16#7FFFFFFF..16#7FFFFFFF. % DuckDB integer -type uint32() :: 0..16#FFFFFFFF. % DuckDB uinteger -type int64() :: -16#7FFFFFFFFFFFFFFF..16#7FFFFFFFFFFFFFFF. % DuckDB bigint -type uint64() :: 0..16#FFFFFFFFFFFFFFFF. % DuckDB ubigint -type hugeint() :: #hugeint{}. % struct, making up a 128 bit signed integer. -type uhugeint() :: #uhugeint{}. % struct, making up a 128 bit unsigned integer. -type second() :: calendar:time() | float(). %% In the range 0.0..60.0 -type time() :: {calendar:hour(), calendar:minute(), second()}. -type datetime() :: {calendar:date(), time()}. -type data() :: boolean() | int8() | int16() | int32() | int64() | uint8() | uint16() | uint32() | uint64() | float() | hugeint() | calendar:date() | time() | datetime() | binary() | list(data()) | #{binary() => data()} | {map, list(data()), list(data())}. -type type_name() :: boolean | tinyint | smallint | integer | bigint | utinyint | usmallint | uinteger | ubigint | float | double | hugeint | uhugeint | timestamp | date | time | interval | varchar | blob | decimal | timestamp_s | timestamp_ms | timestamp_ns | enum | interval | list | struct | map | uuid | json. %% Note: decimal, timestamp_s, timestamp_ms, timestamp_ns and interval's are not supported yet. -type statement_type() :: invalid | select | insert | update | explain | delete | prepare | create | execute | alter | transaction | copy | analyze | variable_set | create_func | drop | export | pragma | vacuum | call | set | load | relation | extension | logical_plan | attach | detach | multi | unknown. -type named_column() :: #{ name := binary(), data := list(data()), type := type_name() }. -type bind_response() :: ok | {error, _}. -type append_response() :: ok | {error, _}. -export_type([database/0, connection/0, prepared_statement/0, result/0, sql/0, type_name/0, statement_type/0, int8/0, int16/0, int32/0, int64/0, uint8/0, uint16/0, uint32/0, uint64/0, idx/0, hugeint/0, uhugeint/0, second/0, time/0, datetime/0 ]). -define(SEC_TO_MICS(S), (S * 1000000)). -define(MIC_TO_SECS(S), (S / 1000000.0)). -define(MIN_TO_MICS(S), (S * 60000000)). -define(HOUR_TO_MICS(S), (S * 3600000000)). -define(EPOCH_OFFSET, 62167219200000000). -on_load(init/0). init() -> NifName = "educkdb", NifFileName = case code:priv_dir(educkdb) of {error, bad_name} -> filename:join("priv", NifName); Dir -> filename:join(Dir, NifName) end, ok = erlang:load_nif(NifFileName, 0). %% %% Startup & Configure %% %% @doc Open, or create a duckdb database with default options. -spec open(Filename) -> Result when Filename :: string(), Result :: {ok, database()} | {error, _}. open(Filename) -> open(Filename, #{}). %% @doc Open, or create a duckdb file %% -spec open(Filename, Options) -> Result when Filename :: string(), Options :: map(), Result :: {ok, database()} | {error, _}. open(_Filename, _Options) -> erlang:nif_error(nif_library_not_loaded). %% @doc Connect to the database. %% Note: It is adviced to use the connection in a single process. %% -spec connect(Database) -> Result when Database :: database(), Result :: {ok, connection()} | {error, _}. connect(_Db) -> erlang:nif_error(nif_library_not_loaded). %% @doc Disconnect from the database. -spec disconnect(Connection) -> Result when Connection :: connection(), Result :: ok | {error, _}. disconnect(_Connection) -> erlang:nif_error(nif_library_not_loaded). %% @doc Close the database. All open connections will become unusable. -spec close(Database) -> Result when Database :: database(), Result :: ok | {error, _}. close(_Db) -> erlang:nif_error(nif_library_not_loaded). %% @doc Return a map with config flags, and explanations. The options %% can vary depending on the underlying DuckDB version used. %% For more information about DuckDB configuration options, see: %% DuckDB Configuration. -spec config_flag_info() -> map(). config_flag_info() -> erlang:nif_error(nif_library_not_loaded). %% %% Query %% %% @doc Query the database. The answer the answer is returned immediately. -spec query(Connection, Sql) -> Result when Connection :: connection(), Sql :: sql(), Result :: {ok, result()} | {error, _}. query(_Conn, _Sql) -> erlang:nif_error(nif_library_not_loaded). %% %% Prepared Statements %% %% @doc Compile, and prepare a statement for later execution. -spec prepare(connection(), sql()) -> {ok, prepared_statement()} | {error, _}. prepare(_Conn, _Sql) -> erlang:nif_error(nif_library_not_loaded). %% @doc Return the statement type of a prepared statement. -spec statement_type(PreparedStatement) -> Result when PreparedStatement :: prepared_statement(), Result :: statement_type(). statement_type(_PreparedStatement) -> erlang:nif_error(nif_library_not_loaded). %% @doc Execute a prepared statement. The answer is returned. -spec execute_prepared(PreparedStatement) -> Result when PreparedStatement :: prepared_statement(), Result :: {ok, result()} | {error, _}. execute_prepared(_PreparedStatement) -> erlang:nif_error(nif_library_not_loaded). %% @doc Return the parameter name of the prepared statement at the specified index. -spec parameter_name(PreparedStatement, Index) -> ParameterName when PreparedStatement :: prepared_statement(), Index :: idx(), ParameterName :: binary(). parameter_name(_PreparedStatement, _Index) -> erlang:nif_error(nif_library_not_loaded). %% @doc Return the parameter type of the prepared statement at the specified index. -spec parameter_type(PreparedStatement, Index) -> ParameterType when PreparedStatement :: prepared_statement(), Index :: idx(), ParameterType :: type_name(). parameter_type(_PreparedStatement, _Index) -> erlang:nif_error(nif_library_not_loaded). %% @doc Return the number of parameters in a prepared statement. -spec parameter_count(PreparedStatement) -> non_neg_integer() when PreparedStatement :: prepared_statement(). parameter_count(_PreparedStatement) -> erlang:nif_error(nif_library_not_loaded). -spec parameter_index(PreparedStatement, Name) -> non_neg_integer() when PreparedStatement :: prepared_statement(), Name :: atom() | binary(). parameter_index(_PreparedStatement, _Name) -> erlang:nif_error(nif_library_not_loaded). %% @doc Return the parameter type of the prepared statement at the specified index. -spec clear_bindings(PreparedStatement) -> ok | error when PreparedStatement :: prepared_statement(). clear_bindings(_PreparedStatement) -> erlang:nif_error(nif_library_not_loaded). %% @doc Bind a boolean to the prepared statement at the specified index. -spec bind_boolean(PreparedStatement, Index, Boolean) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), Boolean :: boolean(), BindResponse :: bind_response(). bind_boolean(Statement, Index, true) -> bind_boolean_intern(Statement, Index, 1); bind_boolean(Statement, Index, false) -> bind_boolean_intern(Statement, Index, 0). -spec bind_boolean_intern(prepared_statement(), idx(), 0..1) -> bind_response(). bind_boolean_intern(_Stmt, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). %% @doc Bind an int8 to the prepared statement at the specified index. -spec bind_int8(PreparedStatement, Index, Int8) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), Int8 :: int8(), BindResponse :: bind_response(). bind_int8(_Stmt, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). %% @doc Bind an int16 to the prepared statement at the specified index. -spec bind_int16(PreparedStatement, Index, Int16) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), Int16 :: int16(), BindResponse :: bind_response(). bind_int16(_Stmt, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). %% @doc Bind an int32 to the prepared statement at the specified index. -spec bind_int32(PreparedStatement, Index, Int32) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), Int32 :: int32(), BindResponse :: bind_response(). bind_int32(_Stmt, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). %% @doc Bind an int64 to the prepared statement at the specified index. -spec bind_int64(PreparedStatement, Index, Int64) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), Int64 :: int64(), BindResponse :: bind_response(). bind_int64(_Stmt, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). %% @doc Bind an uint8 to the prepared statement at the specified index. -spec bind_uint8(PreparedStatement, Index, UInt8) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), UInt8 :: uint8(), BindResponse :: bind_response(). bind_uint8(_Stmt, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). %% @doc Bind an uint8 to the prepared statement at the specified index. -spec bind_uint16(PreparedStatement, Index, UInt16) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), UInt16 :: uint16(), BindResponse :: bind_response(). bind_uint16(_Stmt, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). %% @doc Bind an uint32 to the prepared statement at the specified index. -spec bind_uint32(PreparedStatement, Index, UInt32) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), UInt32 :: uint32(), BindResponse :: bind_response(). bind_uint32(_Stmt, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). %% @doc Bind an uint64 to the prepared statement at the specified index. -spec bind_uint64(PreparedStatement, Index, UInt64) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), UInt64 :: uint64(), BindResponse :: bind_response(). bind_uint64(_Stmt, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). %% @doc Bind an float to the prepared statement at the specified index. Note: Erlang's %% float() datatype is actually a DuckDB double. When binding an Erlang float %% variable you will lose precision. -spec bind_float(PreparedStatement, Index, Float) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), Float :: float(), BindResponse :: bind_response(). bind_float(_Stmt, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). %% @doc Bind an uint64 to the prepared statement at the specified index. Note: Erlang's %% float datatype is a DuckDB double. Using this function allows you to keep the %% precision. -spec bind_double(PreparedStatement, Index, Double) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), Double :: float(), BindResponse :: bind_response(). bind_double(_Stmt, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). %% @doc Bind a date to the prepared statement at the specified index. The date can be %% either given as a calendar:date() tuple, or an integer with the number of %% days since the first of January in the year 0. -spec bind_date(PreparedStatement, Index, Date) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), Date :: calendar:date() | integer(), BindResponse :: bind_response(). bind_date(Stmt, Index, {Y, M, D}=Date) when is_integer(Y) andalso is_integer(M) andalso is_integer(D) -> bind_date_intern(Stmt, Index, calendar:date_to_gregorian_days(Date)); bind_date(Stmt, Index, Days) when is_integer(Days) -> bind_date_intern(Stmt, Index, Days). -spec bind_date_intern(prepared_statement(), idx(), integer()) -> bind_response(). bind_date_intern(_Stmt, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). %% @doc Bind a time to the prepared statement at the specified index. The time can be either %% given as an {hour, minute, second} tuple (similar to calendar:time()), or as an integer %% with the number of microseconds since midnight. -spec bind_time(PreparedStatement, Index, Time) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), Time :: calendar:time() | time() | non_neg_integer(), BindResponse :: bind_response(). bind_time(Stmt, Index, {H, M, S}) -> bind_time_intern(Stmt, Index, ?HOUR_TO_MICS(H) + ?MIN_TO_MICS(M) + floor(?SEC_TO_MICS(S))); bind_time(Stmt, Index, Micros) when is_integer(Micros) -> bind_time_intern(Stmt, Index, Micros). bind_time_intern(_Stmt, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). %% @doc Bind a timestamp to the prepared statement at the specified index. The timestamp %% can be either a datetime tuple, or an integer with the microseconds since 1-Jan in %% the year 0. -spec bind_timestamp(PreparedStatement, Index, TimeStamp) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), TimeStamp :: calendar:datetime() | datetime() | integer(), BindResponse :: bind_response(). bind_timestamp(Stmt, Index, {MegaSecs, Secs, MicroSecs}) -> bind_timestamp_intern(Stmt, Index, ?EPOCH_OFFSET + ?SEC_TO_MICS(MegaSecs * 1000000) + ?SEC_TO_MICS(Secs) + MicroSecs); bind_timestamp(Stmt, Index, {{_, _, _}=Date, {Hour, Minute, Second}}) -> Mics = ?SEC_TO_MICS(calendar:datetime_to_gregorian_seconds({Date, {Hour, Minute, 0}})), RemMics = floor(?SEC_TO_MICS(Second)), bind_timestamp_intern(Stmt, Index, Mics + RemMics); bind_timestamp(Stmt, Index, Micros) when is_integer(Micros) -> bind_timestamp_intern(Stmt, Index, Micros). bind_timestamp_intern(_Stmt, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). % @doc Bind a iolist as varchar to the prepared statement at the specified index. Note: This % function is meant to bind null terminated strings in the database. Not arbitrary % binary data. -spec bind_varchar(PreparedStatement, Index, IOData) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), IOData :: iodata(), BindResponse :: bind_response(). bind_varchar(_Stmt, _Index, _Value) -> erlang:nif_error(nif_library_not_loaded). % @doc Bind a null value to the prepared statement at the specified index. -spec bind_null(PreparedStatement, Index) -> BindResponse when PreparedStatement :: prepared_statement(), Index :: idx(), BindResponse :: bind_response(). bind_null(_Stmt, _Index) -> erlang:nif_error(nif_library_not_loaded). %% %% Results %% %% @doc Get all data chunks from a query result. -spec get_chunks(QueryResult) -> DataChunks when QueryResult :: result(), DataChunks :: list(data_chunk()). get_chunks(_Result) -> erlang:nif_error(nif_library_not_loaded). %% @doc Fetches a data chunk from a result. The function should be called %% repeatedly until the result is exhausted. -spec fetch_chunk(QueryResult) -> DataChunk | '$end' when QueryResult :: result(), DataChunk :: data_chunk(). fetch_chunk(_Result) -> erlang:nif_error(nif_library_not_loaded). %% @doc Get the column names from the query result. -spec column_names(QueryResult) -> Names when QueryResult :: result(), Names :: list(binary()). column_names(_Result) -> erlang:nif_error(nif_library_not_loaded). %% %% Chunks %% %% @doc Return the number of columns in the data chunk. -spec chunk_column_count(DataChunk) -> ColumnCount when DataChunk :: data_chunk(), ColumnCount :: uint64(). chunk_column_count(_Chunk) -> erlang:nif_error(nif_library_not_loaded). %% @doc Get the column types of the chunk %% [todo] spec.. chunk_column_types(_Chunk) -> erlang:nif_error(nif_library_not_loaded). %% @doc Get the columns data of the chunk %% [todo] spec chunk_columns(_Chunk) -> erlang:nif_error(nif_library_not_loaded). %% @doc Return the number of tuples in th data chunk. -spec chunk_size(DataChunk) -> TupleCount when DataChunk :: data_chunk(), TupleCount :: uint64(). chunk_size(_Chunk) -> erlang:nif_error(nif_library_not_loaded). %% %% Appender Interface %% %% @doc Create an appender. Appenders allow for high speed bulk insertions into the database. See %% DuckDB Appender for more information. -spec appender_create(Connection, Schema, Table) -> Result when Connection :: connection(), Schema :: undefined | binary(), Table :: binary, Result :: {ok, appender()} | {error, _}. appender_create(_Connection, _Schema, _Table) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append a boolean value to the current location in the row. -spec append_boolean(Appender, Boolean) -> AppendResponse when Appender :: appender(), Boolean :: boolean(), AppendResponse :: append_response(). append_boolean(Appender, true) -> append_boolean_intern(Appender, 1); append_boolean(Appender, false) -> append_boolean_intern(Appender, 0). -spec append_boolean_intern(appender(), int32()) -> append_response(). append_boolean_intern(_Appender, _Integer) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append a tinyint to the current location in the row. -spec append_int8(Appender, TinyInt) -> AppendResponse when Appender :: appender(), TinyInt :: int8(), AppendResponse :: append_response(). append_int8(_Appender, _Integer) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append a smallint to the current location in the row. -spec append_int16(Appender, SmallInt) -> AppendResponse when Appender :: appender(), SmallInt :: int16(), AppendResponse :: append_response(). append_int16(_Appender, _Integer) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append an integer (32 bit) to the current location in the row. -spec append_int32(Appender, Integer) -> AppendResponse when Appender :: appender(), Integer :: int32(), AppendResponse :: append_response(). append_int32(_Appender, _Integer) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append a bigint to the current location in the row. -spec append_int64(Appender, BigInt) -> AppendResponse when Appender :: appender(), BigInt :: int64(), AppendResponse :: append_response(). append_int64(_Appender, _Integer) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append a utinyint to the current location in the row. -spec append_uint8(Appender, UTinyInt) -> AppendResponse when Appender :: appender(), UTinyInt :: uint8(), AppendResponse :: append_response(). append_uint8(_Appender, _Integer) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append a usmallint to the current location in the row. -spec append_uint16(Appender, USmallInt) -> AppendResponse when Appender :: appender(), USmallInt :: uint16(), AppendResponse :: append_response(). append_uint16(_Appender, _Integer) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append an unsigned integer (32 bit) to the current location in the row. -spec append_uint32(Appender, UInteger) -> AppendResponse when Appender :: appender(), UInteger :: uint32(), AppendResponse :: append_response(). append_uint32(_Appender, _Integer) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append a ubigint to the current location in the row. -spec append_uint64(Appender, UBigInt) -> AppendResponse when Appender :: appender(), UBigInt :: uint64(), AppendResponse :: append_response(). append_uint64(_Appender, _Integer) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append a float to the current location in the row. Note: duckdb floats %% are different than erlang floats. When you add an erlang float to a %% duckdb float column, you will loose precision. -spec append_float(Appender, Float) -> AppendResponse when Appender :: appender(), Float :: float(), AppendResponse :: append_response(). append_float(_Appender, _Integer) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append a double to the current location in the row. Note: duckdb double's %% are the same as erlang floats. -spec append_double(Appender, Double) -> AppendResponse when Appender :: appender(), Double :: float(), AppendResponse :: append_response(). append_double(_Appender, _Integer) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append a time to the current location in the row. -spec append_time(Appender, Time) -> AppendResponse when Appender :: appender(), Time :: calendar:time() | time() | non_neg_integer(), AppendResponse :: append_response(). append_time(Appender, {H, M, S}) -> append_time_intern(Appender, ?HOUR_TO_MICS(H) + ?MIN_TO_MICS(M) + floor(?SEC_TO_MICS(S))); append_time(Appender, Micros) when is_integer(Micros) -> append_time_intern(Appender, Micros). append_time_intern(_Appender, _Time) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append a data to the current location in the row. -spec append_date(Appender, Date) -> AppendResponse when Appender :: appender(), Date :: calendar:date() | integer(), AppendResponse :: append_response(). append_date(Appender, {Y, M, D}=Date) when is_integer(Y) andalso is_integer(M) andalso is_integer(D) -> append_date_intern(Appender, calendar:date_to_gregorian_days(Date)); append_date(Appender, Days) when is_integer(Days) -> append_date_intern(Appender, Days). append_date_intern(_Appender, _Date) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append a timestamp to the current location in the row. -spec append_timestamp(Appender, Timestamp) -> AppendResponse when Appender :: appender(), Timestamp :: calendar:datetime() | erlang:timestamp() | datetime() | integer(), AppendResponse :: append_response(). append_timestamp(Appender, {MegaSecs, Secs, MicroSecs}) -> append_timestamp_intern(Appender, ?EPOCH_OFFSET + ?SEC_TO_MICS(MegaSecs * 1000000) + ?SEC_TO_MICS(Secs) + MicroSecs); append_timestamp(Appender, {{_, _, _}=Date, {Hour, Minute, Second}}) -> Millies = ?SEC_TO_MICS(calendar:datetime_to_gregorian_seconds({Date, {Hour, Minute, 0}})), RemMillies = floor(?SEC_TO_MICS(Second)), append_timestamp_intern(Appender, Millies + RemMillies); append_timestamp(Appender, Micros) when is_integer(Micros) -> append_timestamp_intern(Appender, Micros). append_timestamp_intern(_Appender, _Timestamp) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append a varchar to the current location in the row. -spec append_varchar(Appender, IOData) -> AppendResponse when Appender :: appender(), IOData :: iodata(), AppendResponse :: append_response(). append_varchar(_Appender, _IOData) -> erlang:nif_error(nif_library_not_loaded). %% @doc Append a null value to the current location in the row. -spec append_null(Appender) -> AppendResponse when Appender :: appender(), AppendResponse :: append_response(). append_null(_Appender) -> erlang:nif_error(nif_library_not_loaded). %% @doc Finalize the current row, and start a new one. -spec appender_end_row(Appender) -> AppendResponse when Appender :: appender(), AppendResponse :: append_response(). appender_end_row(_Appender) -> erlang:nif_error(nif_library_not_loaded). %% @doc Flush the appender to the table, forcing the cache of the appender to be cleared %% and the data to be appended to the base table. -spec appender_flush(Appender) -> AppendResult when Appender :: appender(), AppendResult :: append_response(). appender_flush(_Appender) -> erlang:nif_error(nif_library_not_loaded). %% %% Higher Level API %% result_extract(Result) -> case fetch_chunk(Result) of '$end' -> {ok, [], []}; Chunk -> Names = column_names(Result), Types = chunk_column_types(Chunk), Info = column_info(Names, Types), Rows = chunk_rows(Chunk, Result, []), {ok, Info, Rows} end. column_info(Names, Types) -> column_info(Names, Types, []). column_info([], _, Acc) -> lists:reverse(Acc); column_info([N|T], undefined, Acc) -> column_info(T, undefined, [#column{name=N}|Acc]); column_info([Name|RestNames], [Type|RestTypes], Acc) -> column_info(RestNames, RestTypes, [#column{name=Name, type=Type}|Acc]). chunk_rows('$end', _Result, Rows) -> lists:reverse(lists:flatten(Rows)); chunk_rows(Chunk, Result, Rows) -> R = lists:reverse( lists:map(fun list_to_tuple/1, transpose( chunk_columns(Chunk)))), chunk_rows(fetch_chunk(Result), Result, [R | Rows]). %% @doc Do a simple sql query without parameters, and retrieve the result from the %% first data chunk. -spec squery(Connection, Sql) -> Result when Connection :: connection(), Sql :: sql(), Result :: {ok, [ named_column() ]} | {error, _}. squery(Connection, Sql) -> case query(Connection, Sql) of {ok, Result} -> result_extract(Result); {error, _}=E -> E end. %% @doc Execute a prepared statement, and retrieve the first result from the first %% data chunk. -spec execute(PreparedStatement) -> Result when PreparedStatement :: prepared_statement(), Result :: {ok, [ named_column() ]} | {error, _}. execute(Stmt) -> case execute_prepared(Stmt) of {ok, Result} -> result_extract(Result); {error, _}=E -> E end. %% %% Utilities %% transpose([[]|_]) -> []; transpose(M) -> [ lists:map(fun hd/1, M) | transpose(lists:map(fun tl/1, M))]. %% @doc Convert a duckdb hugeint record to an erlang integer. -spec hugeint_to_integer(Hugeint) -> Integer when Hugeint :: hugeint(), Integer :: integer(). hugeint_to_integer(#hugeint{upper=Upper, lower=Lower}) -> (Upper bsl 64) bor Lower. %% @doc Convert an erlang integer to a DuckDB hugeint. %% %% For more information on DuckDB numeric types: See DuckDB Numeric Data Types. -spec integer_to_hugeint(Integer) -> Hugeint when Integer :: integer(), Hugeint :: hugeint(). integer_to_hugeint(Int) -> #hugeint{upper=(Int bsr 64), lower=(Int band 16#FFFFFFFFFFFFFFFF)}. %% @doc Convert a duckdb uhugeint record to an erlang integer. -spec uhugeint_to_integer(UHugeint) -> Integer when UHugeint :: uhugeint(), Integer :: integer(). uhugeint_to_integer(#uhugeint{upper=Upper, lower=Lower}) -> (Upper bsl 64) bor Lower. -spec integer_to_uhugeint(NonNegInteger) -> UHugeint when NonNegInteger :: non_neg_integer(), UHugeint :: uhugeint(). integer_to_uhugeint(Int) when Int >= 0 -> #uhugeint{upper=(Int bsr 64), lower=(Int band 16#FFFFFFFFFFFFFFFF)}. %% @doc Convert a binary represented UUID to a printable hex representation. uuid_binary_to_uuid_string(Bin) -> Formatted = io_lib:format("~8.16.0b-~4.16.0b-~4.16.0b-~2.16.0b~2.16.0b-~12.16.0b", uuid_unpack(Bin)), erlang:iolist_to_binary(Formatted). %% @doc Convert a printable UUID to a binary representation. uuid_string_to_uuid_binary(U)-> uuid_string_to_uuid_binary1(U, <<>>). uuid_string_to_uuid_binary1(<<>>, Acc) -> Acc; uuid_string_to_uuid_binary1(<<$-, Rest/binary>>, Acc) -> uuid_string_to_uuid_binary1(Rest, Acc); uuid_string_to_uuid_binary1(<>, Acc) -> Int = list_to_integer([A,B], 16), uuid_string_to_uuid_binary1(Rest, <>). %uuid_pack(TL, TM, THV, CSHR, CSL, N) -> % <> = <>, % UUID. uuid_unpack(<>) -> [TL, TM, THV, CSHR, CSL, N].