Skip to content
Merged
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,4 @@ link/sdks/typescript/client/.npmrc
/link/kalam-client/sdks/typescript/client/.wasm-target-size-current
/link/kalam-client/sdks/typescript/client/.wasm-target-size-current2
/benchv2/logs
/backend/kalamdb-nightly
54 changes: 53 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions backend/crates/kalamdb-api/src/http/sql/models/sql_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ pub struct QueryRequest {
pub params: Option<Vec<JsonValue>>,

/// Optional namespace ID for unqualified table names.
/// When set, queries like `SELECT * FROM users` resolve to `namespace_id.users`.
/// Set via `USE namespace` command in CLI clients.
/// When set, this request resolves queries like `SELECT * FROM users` to
/// `namespace_id.users`.
/// Interactive clients can populate this after a successful `USE namespace`
/// command; the value is request-scoped on the backend.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub namespace_id: Option<NamespaceId>,
}
Expand Down
20 changes: 20 additions & 0 deletions backend/crates/kalamdb-api/src/http/sql/statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,26 @@ mod tests {
assert_eq!(parsed.sql, "INSERT INTO default.t VALUES (1)");
}

#[test]
fn parse_execute_as_short_alias_quoted_username() {
let parsed = parse_execute_statement(
"EXECUTE AS 'alice' (SELECT * FROM default.todos WHERE id = 1);",
)
.expect("short alias with quoted username should parse");

assert_eq!(parsed.execute_as_username, Some("alice".to_string()));
assert_eq!(parsed.sql, "SELECT * FROM default.todos WHERE id = 1");
}

#[test]
fn parse_execute_as_short_alias_bare_username() {
let parsed = parse_execute_statement("execute as bob (INSERT INTO default.t VALUES (1))")
.expect("short alias with bare username should parse");

assert_eq!(parsed.execute_as_username, Some("bob".to_string()));
assert_eq!(parsed.sql, "INSERT INTO default.t VALUES (1)");
}

#[test]
fn parse_execute_as_user_bare_no_space_before_paren() {
let parsed = parse_execute_statement("EXECUTE AS USER alice(SELECT 1)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,10 @@ impl ExecutionContext {

/// Get the current default namespace (schema) from DataFusion session config
///
/// This reads `datafusion.catalog.default_schema` from the session configuration.
/// The default schema is set to "default" initially and can be changed using:
/// This reads `datafusion.catalog.default_schema` from the request-scoped
/// session configuration.
/// The default schema is set to "default" initially and can be changed within
/// the current request or multi-statement batch using:
/// - `USE namespace`
/// - `USE NAMESPACE namespace`
/// - `SET NAMESPACE namespace`
Expand Down
15 changes: 12 additions & 3 deletions backend/crates/kalamdb-dialect/src/batch_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,15 @@ mod tests {
assert_eq!(statements[1], "SELECT 2");
}

#[test]
fn parse_batch_statements_falls_back_for_short_execute_as() {
let sql = "EXECUTE AS alice (SELECT 1); SELECT 2;";
let statements = parse_batch_statements(sql).unwrap();
assert_eq!(statements.len(), 2);
assert_eq!(statements[0], "EXECUTE AS alice (SELECT 1)");
assert_eq!(statements[1], "SELECT 2");
}

#[test]
fn parse_batch_statements_preserves_file_placeholder_statement() {
let sql = "INSERT INTO uploads SELECT * FROM FILE('orders.csv');";
Expand Down Expand Up @@ -553,20 +562,20 @@ mod tests {

#[test]
fn parse_execution_batch_reports_statement_index() {
let err = parse_execution_batch("SELECT 1; EXECUTE AS USER '' (SELECT 2)").unwrap_err();
let err = parse_execution_batch("SELECT 1; EXECUTE AS '' (SELECT 2)").unwrap_err();
assert_eq!(
err,
ExecutionBatchParseError::Statement {
statement_index: 2,
message: "EXECUTE AS USER username cannot be empty".to_string(),
message: "EXECUTE AS username cannot be empty".to_string(),
}
);
}

#[test]
fn prepare_execution_batch_preserves_execute_as_username() {
let prepared =
prepare_execution_batch("SELECT 1; EXECUTE AS USER alice (SELECT 2)", |statement| {
prepare_execution_batch("SELECT 1; EXECUTE AS alice (SELECT 2)", |statement| {
Ok::<_, String>(statement.sql.clone())
})
.unwrap();
Expand Down
19 changes: 16 additions & 3 deletions backend/crates/kalamdb-dialect/src/ddl/backup_namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,26 @@
//! Parses SQL statements like:
//! - BACKUP DATABASE TO '/backups/kalamdb_backup.tar.gz'
//!
//! Backs up the entire database (data directory + server.toml).
//! Backs up the entire database to either a server-side directory or a
//! `.tar.gz` / `.tgz` archive.

use crate::ddl::DdlResult;

/// BACKUP DATABASE statement
///
/// Backs up the entire database to a compressed archive.
/// Backs up the entire database.
/// The backup includes:
/// - RocksDB data directory
/// - Parquet storage files
/// - Raft snapshots
/// - server.toml configuration
///
/// If the target path ends with `.tar.gz` or `.tgz`, the backup is written as a
/// single archive file. Otherwise KalamDB writes the backup layout into the
/// target directory.
#[derive(Debug, Clone, PartialEq)]
pub struct BackupDatabaseStatement {
/// Backup destination path (should end in .tar.gz)
/// Backup destination path on the server filesystem.
pub backup_path: String,
}

Expand Down Expand Up @@ -125,6 +130,14 @@ mod tests {
assert_eq!(stmt.backup_path, "/backups/kalamdb.tar.gz");
}

#[test]
fn test_parse_backup_database_directory_path() {
let stmt =
BackupDatabaseStatement::parse("BACKUP DATABASE TO '/backups/kalamdb-nightly'")
.unwrap();
assert_eq!(stmt.backup_path, "/backups/kalamdb-nightly");
}

#[test]
fn test_parse_backup_database_missing_to() {
let result = BackupDatabaseStatement::parse("BACKUP DATABASE");
Expand Down
15 changes: 12 additions & 3 deletions backend/crates/kalamdb-dialect/src/ddl/restore_namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@
//! Parses SQL statements like:
//! - RESTORE DATABASE FROM '/backups/kalamdb_backup.tar.gz'
//!
//! Restores the entire database from a compressed archive.
//! Restores the entire database from either a backup directory or a `.tar.gz`
//! / `.tgz` archive.

use crate::ddl::DdlResult;

/// RESTORE DATABASE statement
///
/// Restores the entire database from a compressed archive backup.
/// Restores the entire database from a backup path on the server filesystem.
/// The restore replaces:
/// - RocksDB data directory
/// - Parquet storage files
/// - Raft snapshots
/// - server.toml configuration
#[derive(Debug, Clone, PartialEq)]
pub struct RestoreDatabaseStatement {
/// Backup source path (should be a .tar.gz file)
/// Backup source path on the server filesystem.
pub backup_path: String,
}

Expand Down Expand Up @@ -107,6 +108,14 @@ mod tests {
assert_eq!(stmt.backup_path, "/backups/kalamdb.tar.gz");
}

#[test]
fn test_parse_restore_database_directory_path() {
let stmt =
RestoreDatabaseStatement::parse("RESTORE DATABASE FROM '/backups/kalamdb-nightly'")
.unwrap();
assert_eq!(stmt.backup_path, "/backups/kalamdb-nightly");
}

#[test]
fn test_parse_restore_database_missing_from() {
let result = RestoreDatabaseStatement::parse("RESTORE DATABASE");
Expand Down
Loading
Loading