Packages

Elixir client for ClickHouse database via FINE + clickhouse-cpp (native TCP)

Current section

Files

Jump to
natch native natch_fine src select.cpp
Raw

native/natch_fine/src/select.cpp

#include <fine.hpp>
#include <clickhouse/client.h>
#include <clickhouse/block.h>
#include <clickhouse/columns/column.h>
#include <clickhouse/columns/numeric.h>
#include <clickhouse/columns/string.h>
#include <clickhouse/columns/date.h>
#include <clickhouse/columns/uuid.h>
#include <clickhouse/columns/array.h>
#include <clickhouse/columns/tuple.h>
#include <clickhouse/columns/map.h>
#include <clickhouse/columns/lowcardinality.h>
#include <clickhouse/columns/enum.h>
#include <string>
#include <vector>
#include <memory>
#include <sstream>
#include <iomanip>
using namespace clickhouse;
// Declare that Client is a FINE resource (defined in minimal.cpp)
extern "C" {
FINE_RESOURCE(Client);
}
// Forward declaration
ERL_NIF_TERM column_to_elixir_list(ErlNifEnv *env, ColumnRef col);
// Helper to recursively convert a column to an Elixir list
// This handles all column types including nested arrays
ERL_NIF_TERM column_to_elixir_list(ErlNifEnv *env, ColumnRef col) {
size_t count = col->Size();
std::vector<ERL_NIF_TERM> values;
if (auto uint64_col = col->As<ColumnUInt64>()) {
for (size_t i = 0; i < count; i++) {
values.push_back(enif_make_uint64(env, uint64_col->At(i)));
}
} else if (auto uint32_col = col->As<ColumnUInt32>()) {
for (size_t i = 0; i < count; i++) {
values.push_back(enif_make_uint64(env, uint32_col->At(i)));
}
} else if (auto uint16_col = col->As<ColumnUInt16>()) {
for (size_t i = 0; i < count; i++) {
values.push_back(enif_make_uint64(env, uint16_col->At(i)));
}
} else if (auto uint8_col = col->As<ColumnUInt8>()) {
for (size_t i = 0; i < count; i++) {
values.push_back(enif_make_uint64(env, uint8_col->At(i)));
}
} else if (auto int64_col = col->As<ColumnInt64>()) {
for (size_t i = 0; i < count; i++) {
values.push_back(enif_make_int64(env, int64_col->At(i)));
}
} else if (auto int32_col = col->As<ColumnInt32>()) {
for (size_t i = 0; i < count; i++) {
values.push_back(enif_make_int64(env, int32_col->At(i)));
}
} else if (auto int16_col = col->As<ColumnInt16>()) {
for (size_t i = 0; i < count; i++) {
values.push_back(enif_make_int64(env, int16_col->At(i)));
}
} else if (auto int8_col = col->As<ColumnInt8>()) {
for (size_t i = 0; i < count; i++) {
values.push_back(enif_make_int64(env, int8_col->At(i)));
}
} else if (auto float64_col = col->As<ColumnFloat64>()) {
for (size_t i = 0; i < count; i++) {
values.push_back(enif_make_double(env, float64_col->At(i)));
}
} else if (auto float32_col = col->As<ColumnFloat32>()) {
for (size_t i = 0; i < count; i++) {
values.push_back(enif_make_double(env, float32_col->At(i)));
}
} else if (auto string_col = col->As<ColumnString>()) {
for (size_t i = 0; i < count; i++) {
std::string_view val_view = string_col->At(i);
std::string val(val_view);
ErlNifBinary bin;
enif_alloc_binary(val.size(), &bin);
std::memcpy(bin.data, val.data(), val.size());
values.push_back(enif_make_binary(env, &bin));
}
} else if (auto datetime_col = col->As<ColumnDateTime>()) {
for (size_t i = 0; i < count; i++) {
values.push_back(enif_make_uint64(env, datetime_col->At(i)));
}
} else if (auto datetime64_col = col->As<ColumnDateTime64>()) {
for (size_t i = 0; i < count; i++) {
values.push_back(enif_make_int64(env, datetime64_col->At(i)));
}
} else if (auto date_col = col->As<ColumnDate>()) {
for (size_t i = 0; i < count; i++) {
values.push_back(enif_make_uint64(env, date_col->RawAt(i)));
}
} else if (auto uuid_col = col->As<ColumnUUID>()) {
for (size_t i = 0; i < count; i++) {
UUID uuid = uuid_col->At(i);
std::ostringstream oss;
oss << std::hex << std::setfill('0');
uint64_t high = uuid.first;
oss << std::setw(8) << ((high >> 32) & 0xFFFFFFFF) << "-";
oss << std::setw(4) << ((high >> 16) & 0xFFFF) << "-";
oss << std::setw(4) << (high & 0xFFFF) << "-";
uint64_t low = uuid.second;
oss << std::setw(4) << ((low >> 48) & 0xFFFF) << "-";
oss << std::setw(12) << (low & 0xFFFFFFFFFFFF);
std::string uuid_str = oss.str();
ErlNifBinary bin;
enif_alloc_binary(uuid_str.size(), &bin);
std::memcpy(bin.data, uuid_str.data(), uuid_str.size());
values.push_back(enif_make_binary(env, &bin));
}
} else if (auto decimal_col = col->As<ColumnDecimal>()) {
for (size_t i = 0; i < count; i++) {
Int128 value = decimal_col->At(i);
int64_t scaled_value = static_cast<int64_t>(value);
values.push_back(enif_make_int64(env, scaled_value));
}
} else if (auto array_col = col->As<ColumnArray>()) {
// Recursively handle nested arrays
for (size_t i = 0; i < count; i++) {
auto nested = array_col->GetAsColumn(i);
values.push_back(column_to_elixir_list(env, nested));
}
} else if (auto tuple_col = col->As<ColumnTuple>()) {
// Handle tuple columns - return Elixir tuples
size_t tuple_size = tuple_col->TupleSize();
for (size_t i = 0; i < count; i++) {
std::vector<ERL_NIF_TERM> tuple_elements;
tuple_elements.reserve(tuple_size);
// Extract each element from the tuple
for (size_t j = 0; j < tuple_size; j++) {
auto element_col = tuple_col->At(j);
// Get the i-th value from the j-th element column
// We need to slice this column to get just one value
auto single_value_col = element_col->Slice(i, 1);
// Convert to Elixir and extract the first (only) element
ERL_NIF_TERM elem_list = column_to_elixir_list(env, single_value_col);
// Extract first element from the list
ERL_NIF_TERM head, tail;
if (enif_get_list_cell(env, elem_list, &head, &tail)) {
tuple_elements.push_back(head);
} else {
// Handle empty case - shouldn't happen but be defensive
tuple_elements.push_back(enif_make_atom(env, "error"));
}
}
// Create Elixir tuple from elements
values.push_back(enif_make_tuple_from_array(env, tuple_elements.data(), tuple_elements.size()));
}
} else if (auto map_col = col->As<ColumnMap>()) {
// Handle map columns - return Elixir maps
// Map is stored as Array(Tuple(K, V)) where Tuple is columnar
for (size_t i = 0; i < count; i++) {
// Get the i-th map's tuples as a ColumnTuple
auto kv_tuples = map_col->GetAsColumn(i);
// This is a ColumnTuple with 2 columns: keys and values
if (auto tuple_col = kv_tuples->As<ColumnTuple>()) {
// Get the keys and values columns
auto keys_col = tuple_col->At(0);
auto values_col = tuple_col->At(1);
// The number of key-value pairs is the size of the keys/values columns
size_t map_size = keys_col->Size();
// Build Elixir map
ERL_NIF_TERM elixir_map = enif_make_new_map(env);
// Convert both columns to Elixir lists
ERL_NIF_TERM keys_list = column_to_elixir_list(env, keys_col);
ERL_NIF_TERM values_list = column_to_elixir_list(env, values_col);
// Iterate through both lists simultaneously to build the map
for (size_t j = 0; j < map_size; j++) {
ERL_NIF_TERM key, key_tail, value, value_tail;
if (enif_get_list_cell(env, keys_list, &key, &key_tail) &&
enif_get_list_cell(env, values_list, &value, &value_tail)) {
enif_make_map_put(env, elixir_map, key, value, &elixir_map);
keys_list = key_tail;
values_list = value_tail;
}
}
values.push_back(elixir_map);
} else {
// Fallback for unexpected structure
values.push_back(enif_make_new_map(env));
}
}
} else if (auto enum8_col = col->As<ColumnEnum8>()) {
// Handle Enum8 columns - return string names
for (size_t i = 0; i < count; i++) {
std::string_view name = enum8_col->NameAt(i);
ErlNifBinary bin;
enif_alloc_binary(name.size(), &bin);
std::memcpy(bin.data, name.data(), name.size());
values.push_back(enif_make_binary(env, &bin));
}
} else if (auto enum16_col = col->As<ColumnEnum16>()) {
// Handle Enum16 columns - return string names
for (size_t i = 0; i < count; i++) {
std::string_view name = enum16_col->NameAt(i);
ErlNifBinary bin;
enif_alloc_binary(name.size(), &bin);
std::memcpy(bin.data, name.data(), name.size());
values.push_back(enif_make_binary(env, &bin));
}
} else if (auto lc_col = col->As<ColumnLowCardinality>()) {
// Handle LowCardinality columns - decode values from dictionary
// For each row, get the decoded value by calling GetItem
// GetItem internally looks up the dictionary index and returns the value
for (size_t i = 0; i < count; i++) {
auto item = lc_col->GetItem(i);
// Convert ItemView to Elixir term based on type
if (item.type == Type::String) {
auto val = item.get<std::string_view>();
ErlNifBinary bin;
enif_alloc_binary(val.size(), &bin);
std::memcpy(bin.data, val.data(), val.size());
values.push_back(enif_make_binary(env, &bin));
} else if (item.type == Type::Void) {
// Null value
values.push_back(enif_make_atom(env, "nil"));
} else {
// For other types, would need more handling
// For now, throw an error
throw std::runtime_error("Unsupported LowCardinality inner type");
}
}
} else if (auto nullable_col = col->As<ColumnNullable>()) {
auto nested = nullable_col->Nested();
for (size_t i = 0; i < count; i++) {
if (nullable_col->IsNull(i)) {
values.push_back(enif_make_atom(env, "nil"));
} else {
// Generic handling: slice the nested column to get just the i-th value,
// then recursively convert it. This handles all types uniformly.
auto single_value_col = nested->Slice(i, 1);
ERL_NIF_TERM elem_list = column_to_elixir_list(env, single_value_col);
// Extract first element from the list
ERL_NIF_TERM head, tail;
if (enif_get_list_cell(env, elem_list, &head, &tail)) {
values.push_back(head);
} else {
// Should never happen, but be defensive
values.push_back(enif_make_atom(env, "error"));
}
}
}
}
return enif_make_list_from_array(env, values.data(), values.size());
}
// Helper to convert Block to maps and append to output vector
void block_to_maps_impl(ErlNifEnv *env, std::shared_ptr<Block> block, std::vector<ERL_NIF_TERM>& out_maps) {
size_t col_count = block->GetColumnCount();
size_t row_count = block->GetRowCount();
if (row_count == 0) {
return; // Nothing to add
}
// Extract column names and data
std::vector<std::string> col_names;
std::vector<std::vector<ERL_NIF_TERM>> col_data;
for (size_t c = 0; c < col_count; c++) {
col_names.push_back(block->GetColumnName(c));
ColumnRef col = (*block)[c];
std::vector<ERL_NIF_TERM> column_values;
// Extract column data based on type
if (auto uint64_col = col->As<ColumnUInt64>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_uint64(env, uint64_col->At(i)));
}
} else if (auto uint32_col = col->As<ColumnUInt32>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_uint64(env, uint32_col->At(i)));
}
} else if (auto uint16_col = col->As<ColumnUInt16>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_uint64(env, uint16_col->At(i)));
}
} else if (auto uint8_col = col->As<ColumnUInt8>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_uint64(env, uint8_col->At(i)));
}
} else if (auto int64_col = col->As<ColumnInt64>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_int64(env, int64_col->At(i)));
}
} else if (auto int32_col = col->As<ColumnInt32>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_int64(env, int32_col->At(i)));
}
} else if (auto int16_col = col->As<ColumnInt16>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_int64(env, int16_col->At(i)));
}
} else if (auto int8_col = col->As<ColumnInt8>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_int64(env, int8_col->At(i)));
}
} else if (auto float64_col = col->As<ColumnFloat64>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_double(env, float64_col->At(i)));
}
} else if (auto float32_col = col->As<ColumnFloat32>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_double(env, float32_col->At(i)));
}
} else if (auto string_col = col->As<ColumnString>()) {
for (size_t i = 0; i < row_count; i++) {
std::string_view val_view = string_col->At(i);
std::string val(val_view);
ErlNifBinary bin;
enif_alloc_binary(val.size(), &bin);
std::memcpy(bin.data, val.data(), val.size());
column_values.push_back(enif_make_binary(env, &bin));
}
} else if (auto datetime_col = col->As<ColumnDateTime>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_uint64(env, datetime_col->At(i)));
}
} else if (auto datetime64_col = col->As<ColumnDateTime64>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_int64(env, datetime64_col->At(i)));
}
} else if (auto date_col = col->As<ColumnDate>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_uint64(env, date_col->RawAt(i)));
}
} else if (auto uuid_col = col->As<ColumnUUID>()) {
for (size_t i = 0; i < row_count; i++) {
UUID uuid = uuid_col->At(i);
// Convert UUID to standard string format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
std::ostringstream oss;
oss << std::hex << std::setfill('0');
// high 64 bits
uint64_t high = uuid.first;
oss << std::setw(8) << ((high >> 32) & 0xFFFFFFFF) << "-";
oss << std::setw(4) << ((high >> 16) & 0xFFFF) << "-";
oss << std::setw(4) << (high & 0xFFFF) << "-";
// low 64 bits
uint64_t low = uuid.second;
oss << std::setw(4) << ((low >> 48) & 0xFFFF) << "-";
oss << std::setw(12) << (low & 0xFFFFFFFFFFFF);
std::string uuid_str = oss.str();
ErlNifBinary bin;
enif_alloc_binary(uuid_str.size(), &bin);
std::memcpy(bin.data, uuid_str.data(), uuid_str.size());
column_values.push_back(enif_make_binary(env, &bin));
}
} else if (auto decimal_col = col->As<ColumnDecimal>()) {
for (size_t i = 0; i < row_count; i++) {
Int128 value = decimal_col->At(i);
// Convert Int128 to int64 for Elixir (assumes value fits in int64)
// Elixir will convert back to Decimal by dividing by 10^scale
int64_t scaled_value = static_cast<int64_t>(value);
column_values.push_back(enif_make_int64(env, scaled_value));
}
} else if (auto array_col = col->As<ColumnArray>()) {
// Handle array columns - recursively converts nested arrays to Elixir lists
for (size_t i = 0; i < row_count; i++) {
auto nested = array_col->GetAsColumn(i);
column_values.push_back(column_to_elixir_list(env, nested));
}
} else if (auto map_col = col->As<ColumnMap>()) {
// Handle map columns - use column_to_elixir_list for complex nested structure
for (size_t i = 0; i < row_count; i++) {
auto kv_tuples = map_col->GetAsColumn(i);
if (auto tuple_col = kv_tuples->As<ColumnTuple>()) {
auto keys_col = tuple_col->At(0);
auto values_col = tuple_col->At(1);
size_t map_size = keys_col->Size();
ERL_NIF_TERM elixir_map = enif_make_new_map(env);
ERL_NIF_TERM keys_list = column_to_elixir_list(env, keys_col);
ERL_NIF_TERM values_list = column_to_elixir_list(env, values_col);
for (size_t j = 0; j < map_size; j++) {
ERL_NIF_TERM key, key_tail, value, value_tail;
if (enif_get_list_cell(env, keys_list, &key, &key_tail) &&
enif_get_list_cell(env, values_list, &value, &value_tail)) {
enif_make_map_put(env, elixir_map, key, value, &elixir_map);
keys_list = key_tail;
values_list = value_tail;
}
}
column_values.push_back(elixir_map);
} else {
column_values.push_back(enif_make_new_map(env));
}
}
} else if (auto tuple_col = col->As<ColumnTuple>()) {
// Handle tuple columns - use column_to_elixir_list for complex logic
for (size_t i = 0; i < row_count; i++) {
size_t tuple_size = tuple_col->TupleSize();
std::vector<ERL_NIF_TERM> tuple_elements;
tuple_elements.reserve(tuple_size);
for (size_t j = 0; j < tuple_size; j++) {
auto element_col = tuple_col->At(j);
auto single_value_col = element_col->Slice(i, 1);
ERL_NIF_TERM elem_list = column_to_elixir_list(env, single_value_col);
ERL_NIF_TERM head, tail;
if (enif_get_list_cell(env, elem_list, &head, &tail)) {
tuple_elements.push_back(head);
} else {
tuple_elements.push_back(enif_make_atom(env, "error"));
}
}
column_values.push_back(enif_make_tuple_from_array(env, tuple_elements.data(), tuple_elements.size()));
}
} else if (auto enum8_col = col->As<ColumnEnum8>()) {
// Handle Enum8 columns
for (size_t i = 0; i < row_count; i++) {
std::string_view name = enum8_col->NameAt(i);
ErlNifBinary bin;
enif_alloc_binary(name.size(), &bin);
std::memcpy(bin.data, name.data(), name.size());
column_values.push_back(enif_make_binary(env, &bin));
}
} else if (auto enum16_col = col->As<ColumnEnum16>()) {
// Handle Enum16 columns
for (size_t i = 0; i < row_count; i++) {
std::string_view name = enum16_col->NameAt(i);
ErlNifBinary bin;
enif_alloc_binary(name.size(), &bin);
std::memcpy(bin.data, name.data(), name.size());
column_values.push_back(enif_make_binary(env, &bin));
}
} else if (auto lc_col = col->As<ColumnLowCardinality>()) {
// Handle LowCardinality columns
for (size_t i = 0; i < row_count; i++) {
auto item = lc_col->GetItem(i);
if (item.type == Type::String) {
auto val = item.get<std::string_view>();
ErlNifBinary bin;
enif_alloc_binary(val.size(), &bin);
std::memcpy(bin.data, val.data(), val.size());
column_values.push_back(enif_make_binary(env, &bin));
} else if (item.type == Type::Void) {
column_values.push_back(enif_make_atom(env, "nil"));
} else {
throw std::runtime_error("Unsupported LowCardinality inner type");
}
}
} else if (auto nullable_col = col->As<ColumnNullable>()) {
// Handle nullable columns - use generic approach via column_to_elixir_list
auto nested = nullable_col->Nested();
for (size_t i = 0; i < row_count; i++) {
if (nullable_col->IsNull(i)) {
column_values.push_back(enif_make_atom(env, "nil"));
} else {
// Generic handling: slice the nested column to get just the i-th value,
// then recursively convert it. This handles all types uniformly.
auto single_value_col = nested->Slice(i, 1);
ERL_NIF_TERM elem_list = column_to_elixir_list(env, single_value_col);
// Extract first element from the list
ERL_NIF_TERM head, tail;
if (enif_get_list_cell(env, elem_list, &head, &tail)) {
column_values.push_back(head);
} else {
// Should never happen, but be defensive
column_values.push_back(enif_make_atom(env, "error"));
}
}
}
}
col_data.push_back(column_values);
}
// Pre-create column name atoms once (major optimization)
std::vector<ERL_NIF_TERM> key_atoms;
key_atoms.reserve(col_count);
for (size_t c = 0; c < col_count; c++) {
key_atoms.push_back(enif_make_atom(env, col_names[c].c_str()));
}
// Build maps in local vector first for better cache locality
std::vector<ERL_NIF_TERM> rows;
rows.reserve(row_count);
// Build maps row by row, reusing the pre-created key atoms
for (size_t r = 0; r < row_count; r++) {
ERL_NIF_TERM values[col_count];
for (size_t c = 0; c < col_count; c++) {
values[c] = col_data[c][r];
}
ERL_NIF_TERM map;
enif_make_map_from_arrays(env, key_atoms.data(), values, col_count, &map);
rows.push_back(map);
}
// Append all rows at once to output vector
out_maps.insert(out_maps.end(), rows.begin(), rows.end());
}
// Wrapper struct to return list of maps from FINE NIF
struct SelectResult {
ERL_NIF_TERM maps;
SelectResult(ERL_NIF_TERM m) : maps(m) {}
};
// FINE encoder/decoder for SelectResult
namespace fine {
template <>
struct Encoder<SelectResult> {
static ERL_NIF_TERM encode(ErlNifEnv *env, const SelectResult &result) {
return result.maps;
}
};
template <>
struct Decoder<SelectResult> {
static bool decode(ErlNifEnv *env, ERL_NIF_TERM term, SelectResult &result) {
// This should never be called since SelectResult is only used for return values
return false;
}
};
}
// Execute SELECT query and return list of maps
SelectResult client_select(
ErlNifEnv *env,
fine::ResourcePtr<Client> client,
std::string query) {
// Collect all result maps immediately in the callback
std::vector<ERL_NIF_TERM> all_maps;
client->Select(query, [&](const Block &block) {
// Convert this block to maps and append directly to all_maps
auto block_ptr = std::make_shared<Block>(block);
block_to_maps_impl(env, block_ptr, all_maps);
});
// Build final list from all maps
if (all_maps.empty()) {
return SelectResult(enif_make_list(env, 0));
}
return SelectResult(enif_make_list_from_array(env, all_maps.data(), all_maps.size()));
}
FINE_NIF(client_select, 0);
// Wrapper struct to return columnar map from FINE NIF
struct ColumnarResult {
ERL_NIF_TERM columns_map;
ColumnarResult(ERL_NIF_TERM m) : columns_map(m) {}
};
// FINE encoder/decoder for ColumnarResult
namespace fine {
template <>
struct Encoder<ColumnarResult> {
static ERL_NIF_TERM encode(ErlNifEnv *env, const ColumnarResult &result) {
return result.columns_map;
}
};
template <>
struct Decoder<ColumnarResult> {
static bool decode(ErlNifEnv *env, ERL_NIF_TERM term, ColumnarResult &result) {
return false; // Only used for return values
}
};
}
// Execute SELECT query and return columnar format: %{column_name => [values]}
ColumnarResult client_select_cols(
ErlNifEnv *env,
fine::ResourcePtr<Client> client,
std::string query) {
// Accumulate column data across all blocks
std::map<std::string, std::vector<ERL_NIF_TERM>> all_columns;
client->Select(query, [&](const Block &block) {
size_t col_count = block.GetColumnCount();
size_t row_count = block.GetRowCount();
if (row_count == 0) {
return;
}
// Extract each column's data
for (size_t c = 0; c < col_count; c++) {
std::string col_name = block.GetColumnName(c);
ColumnRef col = block[c];
std::vector<ERL_NIF_TERM> column_values;
column_values.reserve(row_count);
// Extract column data based on type (reuse logic from block_to_maps_impl)
if (auto uint64_col = col->As<ColumnUInt64>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_uint64(env, uint64_col->At(i)));
}
} else if (auto uint32_col = col->As<ColumnUInt32>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_uint64(env, uint32_col->At(i)));
}
} else if (auto uint16_col = col->As<ColumnUInt16>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_uint64(env, uint16_col->At(i)));
}
} else if (auto uint8_col = col->As<ColumnUInt8>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_uint64(env, uint8_col->At(i)));
}
} else if (auto int64_col = col->As<ColumnInt64>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_int64(env, int64_col->At(i)));
}
} else if (auto int32_col = col->As<ColumnInt32>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_int64(env, int32_col->At(i)));
}
} else if (auto int16_col = col->As<ColumnInt16>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_int64(env, int16_col->At(i)));
}
} else if (auto int8_col = col->As<ColumnInt8>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_int64(env, int8_col->At(i)));
}
} else if (auto float64_col = col->As<ColumnFloat64>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_double(env, float64_col->At(i)));
}
} else if (auto float32_col = col->As<ColumnFloat32>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_double(env, float32_col->At(i)));
}
} else if (auto string_col = col->As<ColumnString>()) {
for (size_t i = 0; i < row_count; i++) {
std::string_view val_view = string_col->At(i);
std::string val(val_view);
ErlNifBinary bin;
enif_alloc_binary(val.size(), &bin);
std::memcpy(bin.data, val.data(), val.size());
column_values.push_back(enif_make_binary(env, &bin));
}
} else if (auto datetime_col = col->As<ColumnDateTime>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_uint64(env, datetime_col->At(i)));
}
} else if (auto datetime64_col = col->As<ColumnDateTime64>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_int64(env, datetime64_col->At(i)));
}
} else if (auto date_col = col->As<ColumnDate>()) {
for (size_t i = 0; i < row_count; i++) {
column_values.push_back(enif_make_uint64(env, date_col->RawAt(i)));
}
} else if (auto uuid_col = col->As<ColumnUUID>()) {
for (size_t i = 0; i < row_count; i++) {
UUID uuid = uuid_col->At(i);
std::ostringstream oss;
oss << std::hex << std::setfill('0');
uint64_t high = uuid.first;
oss << std::setw(8) << ((high >> 32) & 0xFFFFFFFF) << "-";
oss << std::setw(4) << ((high >> 16) & 0xFFFF) << "-";
oss << std::setw(4) << (high & 0xFFFF) << "-";
uint64_t low = uuid.second;
oss << std::setw(4) << ((low >> 48) & 0xFFFF) << "-";
oss << std::setw(12) << (low & 0xFFFFFFFFFFFF);
std::string uuid_str = oss.str();
ErlNifBinary bin;
enif_alloc_binary(uuid_str.size(), &bin);
std::memcpy(bin.data, uuid_str.data(), uuid_str.size());
column_values.push_back(enif_make_binary(env, &bin));
}
} else if (auto decimal_col = col->As<ColumnDecimal>()) {
for (size_t i = 0; i < row_count; i++) {
Int128 value = decimal_col->At(i);
int64_t scaled_value = static_cast<int64_t>(value);
column_values.push_back(enif_make_int64(env, scaled_value));
}
} else if (auto array_col = col->As<ColumnArray>()) {
for (size_t i = 0; i < row_count; i++) {
auto nested = array_col->GetAsColumn(i);
column_values.push_back(column_to_elixir_list(env, nested));
}
} else if (auto map_col = col->As<ColumnMap>()) {
for (size_t i = 0; i < row_count; i++) {
auto kv_tuples = map_col->GetAsColumn(i);
if (auto tuple_col = kv_tuples->As<ColumnTuple>()) {
auto keys_col = tuple_col->At(0);
auto values_col = tuple_col->At(1);
size_t map_size = keys_col->Size();
ERL_NIF_TERM elixir_map = enif_make_new_map(env);
ERL_NIF_TERM keys_list = column_to_elixir_list(env, keys_col);
ERL_NIF_TERM values_list = column_to_elixir_list(env, values_col);
for (size_t j = 0; j < map_size; j++) {
ERL_NIF_TERM key, key_tail, value, value_tail;
if (enif_get_list_cell(env, keys_list, &key, &key_tail) &&
enif_get_list_cell(env, values_list, &value, &value_tail)) {
enif_make_map_put(env, elixir_map, key, value, &elixir_map);
keys_list = key_tail;
values_list = value_tail;
}
}
column_values.push_back(elixir_map);
} else {
column_values.push_back(enif_make_new_map(env));
}
}
} else if (auto tuple_col = col->As<ColumnTuple>()) {
for (size_t i = 0; i < row_count; i++) {
size_t tuple_size = tuple_col->TupleSize();
std::vector<ERL_NIF_TERM> tuple_elements;
tuple_elements.reserve(tuple_size);
for (size_t j = 0; j < tuple_size; j++) {
auto element_col = tuple_col->At(j);
auto single_value_col = element_col->Slice(i, 1);
ERL_NIF_TERM elem_list = column_to_elixir_list(env, single_value_col);
ERL_NIF_TERM head, tail;
if (enif_get_list_cell(env, elem_list, &head, &tail)) {
tuple_elements.push_back(head);
} else {
tuple_elements.push_back(enif_make_atom(env, "error"));
}
}
column_values.push_back(enif_make_tuple_from_array(env, tuple_elements.data(), tuple_elements.size()));
}
} else if (auto enum8_col = col->As<ColumnEnum8>()) {
for (size_t i = 0; i < row_count; i++) {
std::string_view name = enum8_col->NameAt(i);
ErlNifBinary bin;
enif_alloc_binary(name.size(), &bin);
std::memcpy(bin.data, name.data(), name.size());
column_values.push_back(enif_make_binary(env, &bin));
}
} else if (auto enum16_col = col->As<ColumnEnum16>()) {
for (size_t i = 0; i < row_count; i++) {
std::string_view name = enum16_col->NameAt(i);
ErlNifBinary bin;
enif_alloc_binary(name.size(), &bin);
std::memcpy(bin.data, name.data(), name.size());
column_values.push_back(enif_make_binary(env, &bin));
}
} else if (auto lc_col = col->As<ColumnLowCardinality>()) {
for (size_t i = 0; i < row_count; i++) {
auto item = lc_col->GetItem(i);
if (item.type == Type::String) {
auto val = item.get<std::string_view>();
ErlNifBinary bin;
enif_alloc_binary(val.size(), &bin);
std::memcpy(bin.data, val.data(), val.size());
column_values.push_back(enif_make_binary(env, &bin));
} else if (item.type == Type::Void) {
column_values.push_back(enif_make_atom(env, "nil"));
} else {
throw std::runtime_error("Unsupported LowCardinality inner type");
}
}
} else if (auto nullable_col = col->As<ColumnNullable>()) {
auto nested = nullable_col->Nested();
for (size_t i = 0; i < row_count; i++) {
if (nullable_col->IsNull(i)) {
column_values.push_back(enif_make_atom(env, "nil"));
} else {
auto single_value_col = nested->Slice(i, 1);
ERL_NIF_TERM elem_list = column_to_elixir_list(env, single_value_col);
ERL_NIF_TERM head, tail;
if (enif_get_list_cell(env, elem_list, &head, &tail)) {
column_values.push_back(head);
} else {
column_values.push_back(enif_make_atom(env, "error"));
}
}
}
}
// Append this block's column values to accumulated data
all_columns[col_name].insert(
all_columns[col_name].end(),
column_values.begin(),
column_values.end()
);
}
});
// Build Elixir map: %{column_name => [values]}
size_t num_columns = all_columns.size();
std::vector<ERL_NIF_TERM> keys;
std::vector<ERL_NIF_TERM> values;
keys.reserve(num_columns);
values.reserve(num_columns);
for (const auto& [col_name, col_values] : all_columns) {
keys.push_back(enif_make_atom(env, col_name.c_str()));
values.push_back(enif_make_list_from_array(env, col_values.data(), col_values.size()));
}
ERL_NIF_TERM columns_map;
enif_make_map_from_arrays(env, keys.data(), values.data(), num_columns, &columns_map);
return ColumnarResult(columns_map);
}
FINE_NIF(client_select_cols, 0);