Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Plugins/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
cmake_minimum_required(VERSION 3.25)

add_subdirectory(chronokvs)
add_subdirectory(chronosql)

# TODO(#517): Re-enable ChronoStream plugin build/install once the plugin is cleaned up
# and properly integrated/packaged. It is temporarily disabled to reduce noise and
Expand Down
72 changes: 72 additions & 0 deletions Plugins/chronosql/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
cmake_minimum_required(VERSION 3.25)

project(chronosql
VERSION 0.1.0
DESCRIPTION "ChronoSQL Plugin for ChronoLog"
LANGUAGES CXX
)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

include(GNUInstallDirs)

add_library(chronosql
src/chronosql.cpp
src/chronosql_mapper.cpp
src/chronosql_client_adapter.cpp
src/chronosql_metadata.cpp
src/chronosql_row_codec.cpp
src/chronosql_sql_parser.cpp
)

target_include_directories(chronosql
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CHRONOLOG_INSTALL_INCLUDE_DIR}>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)

# Public headers for installation
file(GLOB HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h")
set_target_properties(chronosql PROPERTIES
PUBLIC_HEADER "${HEADER_FILES}"
)

set_target_properties(chronosql PROPERTIES
VERSION ${CHRONOLOG_PACKAGE_VERSION}
SOVERSION ${CHRONOLOG_VERSION_MAJOR}
)

# Bring in chronolog_client's interface headers (json-c, ClientConfiguration, ...).
target_include_directories(chronosql PRIVATE
$<TARGET_PROPERTY:chronolog_client,INTERFACE_INCLUDE_DIRECTORIES>
)

find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(JSON-C REQUIRED json-c)

target_include_directories(chronosql PRIVATE ${JSON-C_INCLUDE_DIRS})
target_link_directories(chronosql PRIVATE ${JSON-C_LIBRARY_DIRS})

target_link_libraries(chronosql
PRIVATE
chronolog_client
${JSON-C_LIBRARIES}
rt
PUBLIC
Threads::Threads
)

option(CHRONOSQL_BUILD_EXAMPLES "Build ChronoSQL examples" ON)
if(CHRONOSQL_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()

chronolog_install_target(chronosql)

message(STATUS "ChronoSQL: superbuild mode enabled")
30 changes: 30 additions & 0 deletions Plugins/chronosql/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# ChronoSQL runnable examples (installed as chrono-chronosql-example-<role>)
add_executable(chronosql_writer_example chronosql_writer_example.cpp)
add_executable(chronosql_reader_example chronosql_reader_example.cpp)

target_link_libraries(chronosql_writer_example PRIVATE chronosql)
target_link_libraries(chronosql_reader_example PRIVATE chronosql)

# cmd_arg_parse.h lives in chronolog_client's public include dir. chronosql
# consumes chronolog_client privately, so pull its include interface in
# directly here for the examples that use the helper.
target_include_directories(chronosql_writer_example PRIVATE
$<TARGET_PROPERTY:chronolog_client,INTERFACE_INCLUDE_DIRECTORIES>
)
target_include_directories(chronosql_reader_example PRIVATE
$<TARGET_PROPERTY:chronolog_client,INTERFACE_INCLUDE_DIRECTORIES>
)

set_target_properties(chronosql_writer_example PROPERTIES
OUTPUT_NAME chrono-chronosql-example-writer
BUILD_WITH_INSTALL_RPATH TRUE
SKIP_BUILD_RPATH TRUE
)
set_target_properties(chronosql_reader_example PROPERTIES
OUTPUT_NAME chrono-chronosql-example-reader
BUILD_WITH_INSTALL_RPATH TRUE
SKIP_BUILD_RPATH TRUE
)

chronolog_install_target(chronosql_writer_example DESTINATION examples)
chronolog_install_target(chronosql_reader_example DESTINATION examples)
41 changes: 41 additions & 0 deletions Plugins/chronosql/examples/chronosql_reader_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <cstdlib>
#include <iostream>
#include <string>

#include <cmd_arg_parse.h>

#include "chronosql.h"

int main(int argc, char** argv)
{
std::string conf_file_path = parse_conf_path_arg(argc, argv);

auto db = conf_file_path.empty() ? chronosql::ChronoSQL::Create() : chronosql::ChronoSQL::Create(conf_file_path);
if(!db)
{
std::cerr << "Failed to initialize ChronoSQL\n";
return EXIT_FAILURE;
}

auto schema = db->getSchema("users");
if(!schema)
{
std::cerr << "Table 'users' not found. Run the writer example first.\n";
return EXIT_FAILURE;
}

auto result = db->execute("SELECT * FROM users");
std::cout << "Found " << result.rows.size() << " rows in 'users':\n";
for(const auto& row: result.rows)
{
std::cout << " ts=" << row.timestamp;
for(const auto& col: result.columns)
{
auto it = row.values.find(col);
std::cout << " " << col << "=" << (it == row.values.end() ? "NULL" : it->second);
}
std::cout << "\n";
}

return EXIT_SUCCESS;
}
39 changes: 39 additions & 0 deletions Plugins/chronosql/examples/chronosql_writer_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <string>

#include <cmd_arg_parse.h>

#include "chronosql.h"

int main(int argc, char** argv)
{
// Optional ChronoLog client config file (-c/--config). Same convention as
// the ChronoKVS examples; when omitted, ChronoSQL uses the localhost
// defaults.
std::string conf_file_path = parse_conf_path_arg(argc, argv);

auto db = conf_file_path.empty() ? chronosql::ChronoSQL::Create() : chronosql::ChronoSQL::Create(conf_file_path);
if(!db)
{
std::cerr << "Failed to initialize ChronoSQL\n";
return EXIT_FAILURE;
}

// Programmatic API path: create a small "users" table.
if(!db->getSchema("users").has_value())
{
db->createTable("users", {{"id", chronosql::ColumnType::INT}, {"name", chronosql::ColumnType::STRING}});
}

// SQL string facade path: insert via execute().
auto r1 = db->execute("INSERT INTO users (id, name) VALUES (1, 'alice')");
auto r2 = db->execute("INSERT INTO users VALUES (2, 'bob')");

std::cout << "Inserted ts=" << r1.last_insert_timestamp.value_or(0)
<< " and ts=" << r2.last_insert_timestamp.value_or(0) << "\n";

db->flush();
return EXIT_SUCCESS;
}
160 changes: 160 additions & 0 deletions Plugins/chronosql/include/chronosql.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#ifndef CHRONOSQL_H_
#define CHRONOSQL_H_

#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "chronosql_logger.h"
#include "chronosql_types.h"

namespace chronosql
{

class ChronoSQLMapper;

/**
* @brief SQL-style facade over a single ChronoLog chronicle.
*
* A ChronoSQL "database" is one chronicle. Each table is one story inside that
* chronicle, and each row is one event whose `log_record` is a JSON object of
* column values. A reserved metadata story persists CREATE/DROP TABLE
* operations so the schema and table list are recoverable across processes.
*
* The implicit `__ts` pseudo-column exposes the ChronoLog event timestamp and
* can be used in time-range queries (e.g. WHERE __ts BETWEEN a AND b).
*/
class ChronoSQL
{
private:
std::unique_ptr<ChronoSQLMapper> mapper;
LogLevel logLevel_;

// Private constructors. May throw on configuration or connection failure;
// the public Create() factories catch those exceptions at the library
// boundary and signal failure via a nullptr return.
explicit ChronoSQL(LogLevel level);
explicit ChronoSQL(const std::string& config_path, LogLevel level);

public:
/**
* @brief Create a ChronoSQL instance using built-in default ChronoLog
* client configuration (localhost deployment).
*
* This factory does not propagate exceptions across the library boundary:
* any failure during configuration loading or ChronoLog connection is
* logged at the configured @p level and signalled by returning nullptr.
*
* @param level
* The logging level. Default is DEBUG in debug builds, ERROR in release builds.
*
* @return std::unique_ptr<ChronoSQL>
* A connected ChronoSQL instance, or nullptr if construction failed.
*/
static std::unique_ptr<ChronoSQL> Create(LogLevel level = getDefaultLogLevel()) noexcept;

/**
* @brief Create a ChronoSQL instance loading a ChronoLog client config.
*
* Loads the JSON configuration at @p config_path and uses the resulting portal,
* query and logging settings to connect to ChronoLog. Pass an empty string to
* fall back to the built-in defaults (localhost deployment).
*
* This factory does not propagate exceptions across the library boundary:
* any failure during configuration loading or ChronoLog connection is
* logged at the configured @p level and signalled by returning nullptr.
*
* @param config_path
* Path to a ChronoLog client configuration JSON file. Empty means
* "use defaults".
* @param level
* The logging level. Default is DEBUG in debug builds, ERROR in release builds.
*
* @return std::unique_ptr<ChronoSQL>
* A connected ChronoSQL instance, or nullptr if @p config_path could
* not be loaded or the ChronoLog connection failed.
*/
static std::unique_ptr<ChronoSQL> Create(const std::string& config_path,
LogLevel level = getDefaultLogLevel()) noexcept;

~ChronoSQL();

LogLevel getLogLevel() const { return logLevel_; }

// ---- Programmatic engine API ------------------------------------------

/**
* @brief Create a new table with the given schema.
*
* @throws std::invalid_argument on empty/duplicate columns or reserved
* column names (any name starting with `__`).
* @throws std::runtime_error if the table already exists or the metadata
* append fails.
*/
void createTable(const std::string& name, const std::vector<Column>& columns);

/**
* @brief Drop a table. The underlying story is not destroyed; only the
* metadata mapping is removed (an event log is append-only).
*
* @throws std::runtime_error if the table does not exist.
*/
void dropTable(const std::string& name);

/// List all currently known table names (in CREATE order).
std::vector<std::string> listTables();

/// Return the schema for a table, or std::nullopt if unknown.
std::optional<Schema> getSchema(const std::string& table);

/**
* @brief Insert a row into @p table. Column names not in the schema are
* rejected. Missing columns are treated as NULL (omitted from the
* persisted JSON).
*
* @return The ChronoLog event timestamp assigned to this row.
*/
std::uint64_t insert(const std::string& table, const std::map<std::string, std::string>& row);

/// Equality lookup on an arbitrary column. O(events) — no auxiliary index.
std::vector<Row> selectByKey(const std::string& table, const std::string& column, const std::string& value);

/// Time-range scan on the implicit `__ts` column.
/// Returns rows with `__ts` in [start_ts, end_ts).
std::vector<Row> selectRange(const std::string& table, std::uint64_t start_ts, std::uint64_t end_ts);

/// Full-table scan.
std::vector<Row> selectAll(const std::string& table);

// ---- SQL string facade ------------------------------------------------

/**
* @brief Parse and execute a single SQL statement.
*
* Supported grammar (case-insensitive keywords):
* - CREATE TABLE name (col [type], col [type], ...)
* - DROP TABLE name
* - INSERT INTO name [(col, col, ...)] VALUES (v, v, ...)
* - SELECT * | col, ... FROM name
* [WHERE col = value | WHERE __ts BETWEEN n AND n]
*
* Types: INT | DOUBLE | STRING | BOOL (synonyms: VARCHAR, TEXT → STRING).
*
* @throws std::invalid_argument on parse errors.
* @throws std::runtime_error on execution errors.
*/
ResultSet execute(const std::string& sql);

/**
* @brief Flush all cached write story handles, releasing them so writes
* become visible to subsequent reads. Equivalent to the chronokvs flush().
*/
void flush();
};

} // namespace chronosql

#endif // CHRONOSQL_H_
Loading
Loading