code: dbal,hpp,cpp (16 files)

This commit is contained in:
2025-12-26 01:30:51 +00:00
parent f3d95d3dbf
commit e3df845abd
16 changed files with 465 additions and 46 deletions

View File

@@ -0,0 +1,28 @@
#pragma once
/**
* @file ast.hpp
* @brief AST class (wrapper)
*/
#include "ast_node.hpp"
#include "ast_add_child.hpp"
#include "ast_to_string.hpp"
namespace dbal::query {
/**
* AST wrapper class
*/
class AST {
public:
std::shared_ptr<ASTNode> root;
AST() : root(nullptr) {}
explicit AST(std::shared_ptr<ASTNode> r) : root(r) {}
std::string toString() const {
return ast_node_to_string(root);
}
};
} // namespace dbal::query

View File

@@ -0,0 +1,20 @@
#pragma once
/**
* @file ast_add_child.hpp
* @brief Add child to AST node
*/
#include "ast_node.hpp"
namespace dbal::query {
/**
* Add a child node to parent
* @param parent Parent node
* @param child Child node to add
*/
inline void ast_add_child(ASTNode& parent, std::shared_ptr<ASTNode> child) {
parent.children.push_back(child);
}
} // namespace dbal::query

View File

@@ -0,0 +1,32 @@
#pragma once
/**
* @file ast_node.hpp
* @brief AST node type and structure
*/
#include <string>
#include <vector>
#include <memory>
namespace dbal::query {
enum class NodeType {
Select,
Insert,
Update,
Delete,
Where,
Join,
OrderBy,
Limit
};
struct ASTNode {
NodeType type;
std::string value;
std::vector<std::shared_ptr<ASTNode>> children;
ASTNode(NodeType t, const std::string& v = "") : type(t), value(v) {}
};
} // namespace dbal::query

View File

@@ -0,0 +1,26 @@
#pragma once
/**
* @file ast_to_string.hpp
* @brief Convert AST to string
*/
#include "ast_node.hpp"
namespace dbal::query {
/**
* Convert AST node to string representation
* @param node Node to convert
* @return String representation
*/
inline std::string ast_node_to_string(const std::shared_ptr<ASTNode>& node) {
if (!node) return "";
std::string result = node->value;
for (const auto& child : node->children) {
result += " " + ast_node_to_string(child);
}
return result;
}
} // namespace dbal::query

View File

@@ -0,0 +1,56 @@
#pragma once
/**
* @file builder.hpp
* @brief QueryBuilder class (wrapper)
*/
#include "builder_state.hpp"
#include "builder_select.hpp"
#include "builder_from.hpp"
#include "builder_where.hpp"
#include "builder_order_by.hpp"
#include "builder_limit.hpp"
#include "builder_build.hpp"
namespace dbal::query {
/**
* Query builder class
* Thin wrapper around builder functions
*/
class QueryBuilder {
public:
QueryBuilder& select(const std::vector<std::string>& columns) {
builder_select(state_, columns);
return *this;
}
QueryBuilder& from(const std::string& table) {
builder_from(state_, table);
return *this;
}
QueryBuilder& where(const std::string& condition) {
builder_where(state_, condition);
return *this;
}
QueryBuilder& orderBy(const std::string& column, const std::string& direction = "ASC") {
builder_order_by(state_, column, direction);
return *this;
}
QueryBuilder& limit(int lim) {
builder_limit(state_, lim);
return *this;
}
std::string build() const {
return builder_build(state_);
}
private:
BuilderState state_;
};
} // namespace dbal::query

View File

@@ -0,0 +1,49 @@
#pragma once
/**
* @file builder_build.hpp
* @brief Build query string from state
*/
#include "builder_state.hpp"
namespace dbal::query {
/**
* Build SQL query string from state
* @param state Builder state
* @return SQL query string
*/
inline std::string builder_build(const BuilderState& state) {
std::string query = state.query_type + " ";
if (!state.columns.empty()) {
for (size_t i = 0; i < state.columns.size(); ++i) {
query += state.columns[i];
if (i < state.columns.size() - 1) query += ", ";
}
} else {
query += "*";
}
query += " FROM " + state.table;
if (!state.conditions.empty()) {
query += " WHERE ";
for (size_t i = 0; i < state.conditions.size(); ++i) {
query += state.conditions[i];
if (i < state.conditions.size() - 1) query += " AND ";
}
}
if (!state.order_by.empty()) {
query += " ORDER BY " + state.order_by;
}
if (state.limit > 0) {
query += " LIMIT " + std::to_string(state.limit);
}
return query;
}
} // namespace dbal::query

View File

@@ -0,0 +1,20 @@
#pragma once
/**
* @file builder_from.hpp
* @brief Set FROM table
*/
#include "builder_state.hpp"
namespace dbal::query {
/**
* Set table for query
* @param state Builder state
* @param table Table name
*/
inline void builder_from(BuilderState& state, const std::string& table) {
state.table = table;
}
} // namespace dbal::query

View File

@@ -0,0 +1,20 @@
#pragma once
/**
* @file builder_limit.hpp
* @brief Set LIMIT clause
*/
#include "builder_state.hpp"
namespace dbal::query {
/**
* Set LIMIT clause
* @param state Builder state
* @param limit Max rows
*/
inline void builder_limit(BuilderState& state, int limit) {
state.limit = limit;
}
} // namespace dbal::query

View File

@@ -0,0 +1,21 @@
#pragma once
/**
* @file builder_order_by.hpp
* @brief Set ORDER BY clause
*/
#include "builder_state.hpp"
namespace dbal::query {
/**
* Set ORDER BY clause
* @param state Builder state
* @param column Column to order by
* @param direction ASC or DESC
*/
inline void builder_order_by(BuilderState& state, const std::string& column, const std::string& direction = "ASC") {
state.order_by = column + " " + direction;
}
} // namespace dbal::query

View File

@@ -0,0 +1,21 @@
#pragma once
/**
* @file builder_select.hpp
* @brief Set SELECT columns
*/
#include "builder_state.hpp"
namespace dbal::query {
/**
* Set columns for SELECT query
* @param state Builder state
* @param columns Columns to select
*/
inline void builder_select(BuilderState& state, const std::vector<std::string>& columns) {
state.query_type = "SELECT";
state.columns = columns;
}
} // namespace dbal::query

View File

@@ -0,0 +1,24 @@
#pragma once
/**
* @file builder_state.hpp
* @brief Query builder state
*/
#include <string>
#include <vector>
namespace dbal::query {
/**
* Query builder state
*/
struct BuilderState {
std::string query_type;
std::vector<std::string> columns;
std::string table;
std::vector<std::string> conditions;
std::string order_by;
int limit = 0;
};
} // namespace dbal::query

View File

@@ -0,0 +1,20 @@
#pragma once
/**
* @file builder_where.hpp
* @brief Add WHERE condition
*/
#include "builder_state.hpp"
namespace dbal::query {
/**
* Add WHERE condition
* @param state Builder state
* @param condition Condition string
*/
inline void builder_where(BuilderState& state, const std::string& condition) {
state.conditions.push_back(condition);
}
} // namespace dbal::query

View File

@@ -0,0 +1,22 @@
#pragma once
/**
* @file query_normalize.hpp wrapper
* @brief QueryNormalizer class
*/
#include "query_normalize.hpp"
namespace dbal::query {
/**
* Query normalizer class
* Thin wrapper around normalize functions
*/
class QueryNormalizer {
public:
static std::string normalize(const std::string& query) {
return query_normalize(query);
}
};
} // namespace dbal::query

View File

@@ -0,0 +1,58 @@
#pragma once
/**
* @file query_normalize.hpp
* @brief Query string normalization
*/
#include <string>
#include <algorithm>
#include <cctype>
namespace dbal::query {
/**
* Remove extra whitespace from string
* @param str Input string
* @return String with single spaces
*/
inline std::string query_remove_extra_whitespace(const std::string& str) {
std::string result;
bool lastWasSpace = false;
for (char c : str) {
if (std::isspace(static_cast<unsigned char>(c))) {
if (!lastWasSpace) {
result += ' ';
lastWasSpace = true;
}
} else {
result += c;
lastWasSpace = false;
}
}
size_t start = result.find_first_not_of(' ');
size_t end = result.find_last_not_of(' ');
if (start != std::string::npos && end != std::string::npos) {
return result.substr(start, end - start + 1);
}
return result;
}
/**
* Normalize a query string (uppercase, trim whitespace)
* @param query Input query
* @return Normalized query
*/
inline std::string query_normalize(const std::string& query) {
std::string normalized = query;
std::transform(normalized.begin(), normalized.end(), normalized.begin(),
[](unsigned char c) { return std::toupper(c); });
return query_remove_extra_whitespace(normalized);
}
} // namespace dbal::query

View File

@@ -2,25 +2,27 @@
* @file delete-user.ts
* @description Delete user operation
*/
import type { Result } from '../types';
import type { InMemoryStore } from '../store/in-memory-store';
import type { Result } from '../../types'
import type { InMemoryStore } from '../../store/in-memory-store'
import { validateId } from '../../validation/validate-id'
/**
* Delete a user by ID
*/
export async function deleteUser(store: InMemoryStore, id: string): Promise<Result<boolean>> {
if (!id) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: 'ID required' } };
export const deleteUser = async (store: InMemoryStore, id: string): Promise<Result<boolean>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
}
const user = store.users.get(id);
const user = store.users.get(id)
if (!user) {
return { success: false, error: { code: 'NOT_FOUND', message: `User not found: ${id}` } };
return { success: false, error: { code: 'NOT_FOUND', message: `User not found: ${id}` } }
}
store.userEmails.delete(user.email);
store.userUsernames.delete(user.username);
store.users.delete(id);
store.users.delete(id)
store.usersByEmail.delete(user.email)
store.usersByUsername.delete(user.username)
return { success: true, data: true };
return { success: true, data: true }
}

View File

@@ -2,57 +2,57 @@
* @file update-user.ts
* @description Update user operation
*/
import type { User, UpdateUserInput, Result } from '../types';
import type { InMemoryStore } from '../store/in-memory-store';
import { validateEmail, validateUsername } from '../validation/user-validation';
import type { Result, UpdateUserInput, User } from '../../types'
import type { InMemoryStore } from '../../store/in-memory-store'
import { validateId } from '../../validation/validate-id'
import { validateUserUpdate } from '../../validation/validate-user-update'
/**
* Update an existing user
*/
export async function updateUser(
export const updateUser = async (
store: InMemoryStore,
id: string,
input: UpdateUserInput
): Promise<Result<User>> {
if (!id) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: 'ID required' } };
): Promise<Result<User>> => {
const idErrors = validateId(id)
if (idErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: idErrors[0] } }
}
const user = store.users.get(id);
const user = store.users.get(id)
if (!user) {
return { success: false, error: { code: 'NOT_FOUND', message: `User not found: ${id}` } };
return { success: false, error: { code: 'NOT_FOUND', message: `User not found: ${id}` } }
}
if (input.email !== undefined) {
if (!validateEmail(input.email)) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: 'Invalid email' } };
}
const existingId = store.userEmails.get(input.email);
if (existingId && existingId !== id) {
return { success: false, error: { code: 'CONFLICT', message: 'Email exists' } };
}
store.userEmails.delete(user.email);
store.userEmails.set(input.email, id);
user.email = input.email;
const validationErrors = validateUserUpdate(input)
if (validationErrors.length > 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: validationErrors[0] } }
}
if (input.username !== undefined) {
if (!validateUsername(input.username)) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: 'Invalid username' } };
if (input.username && input.username !== user.username) {
if (store.usersByUsername.has(input.username)) {
return { success: false, error: { code: 'CONFLICT', message: 'Username already exists' } }
}
const existingId = store.userUsernames.get(input.username);
if (existingId && existingId !== id) {
return { success: false, error: { code: 'CONFLICT', message: 'Username exists' } };
}
store.userUsernames.delete(user.username);
store.userUsernames.set(input.username, id);
user.username = input.username;
store.usersByUsername.delete(user.username)
store.usersByUsername.set(input.username, id)
user.username = input.username
}
if (input.name !== undefined) user.name = input.name;
if (input.level !== undefined) user.level = input.level;
if (input.isActive !== undefined) user.isActive = input.isActive;
if (input.email && input.email !== user.email) {
if (store.usersByEmail.has(input.email)) {
return { success: false, error: { code: 'CONFLICT', message: 'Email already exists' } }
}
store.usersByEmail.delete(user.email)
store.usersByEmail.set(input.email, id)
user.email = input.email
}
user.updatedAt = new Date();
return { success: true, data: user };
if (input.role) {
user.role = input.role
}
user.updatedAt = new Date()
return { success: true, data: user }
}