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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ examples/simple_neural_network.cpp

cmake-build-debug/

.claude/
.idea/

/compile
Expand Down
170 changes: 170 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## About sqlite_orm

sqlite_orm is a header-only C++17 ORM library for SQLite. It provides type-safe database operations without raw SQL strings, using C++ template metaprogramming to map C++ structs to database tables.

**Key characteristics:**
- Header-only library (main header: `include/sqlite_orm/sqlite_orm.h`)
- Requires C++17 minimum (supports C++20, C++23, C++26)
- Single dependency: libsqlite3
- License: AGPL for open source, MIT after purchase

## Build System

### Building the project
```bash
# Configure with default C++17
cmake -B build

# Configure with specific C++ standard
cmake -B build -DSQLITE_ORM_ENABLE_CXX_20=ON
cmake -B build -DSQLITE_ORM_ENABLE_CXX_23=ON

# Build
cmake --build build

# Install (may need admin rights)
cmake --build build --target install
```

### Running tests
```bash
# Build tests (from project root)
cmake -B build -DBUILD_TESTING=ON
cmake --build build

# Run all tests
cd build && ctest

# Run unit tests directly (with more control)
./build/tests/unit_tests

# Run specific test by name using Catch2 filter
./build/tests/unit_tests "Limits"
./build/tests/unit_tests "[ast_iterator]"
```

The test suite uses Catch2 framework. All test files are in `tests/` directory. Tests are organized using `TEST_CASE` and `SECTION` macros.

### Building examples
```bash
cmake -B build -DBUILD_EXAMPLES=ON
cmake --build build
```

## Architecture Overview

### Core Design Pattern
The library uses a **storage-centric architecture** with compile-time type safety through template metaprogramming:

1. **Storage object** (`dev/storage.h`, `dev/storage_base.h`): The main interface that provides CRUD operations, query building, and schema management. Created via `make_storage()`.

2. **Table definitions** (`dev/schema/table.h`): Tables are defined using `make_table()` which maps C++ structs to database schema. Columns are mapped using member pointers.

3. **Type system** (`dev/type_traits.h`, `dev/type_printer.h`): Extensive compile-time type introspection to deduce types from member pointers and validate queries at compile time.

4. **Statement serialization** (`dev/statement_serializer.h`, `dev/serializer_context.h`): Converts C++ expression objects into SQL strings.

5. **Expression objects** (`dev/conditions.h`, `dev/core_functions.h`, `dev/ast/`): Type-safe representations of SQL operations (WHERE, JOIN, ORDER BY, etc.).

### Key Implementation Files

**Storage layer:**
- `dev/storage.h` - Main storage template with CRUD operations
- `dev/storage_base.h` - Base class with connection management, transactions, UDFs
- `dev/storage_impl.h` - Implementation details
- `dev/connection_holder.h` - RAII wrapper for sqlite3 connections

**Schema definition:**
- `dev/schema/table.h` - Table definitions
- `dev/schema/column.h` - Column definitions and constraints
- `dev/schema/view.h` - View support
- `dev/schema/virtual_table.h` - Virtual table support
- `dev/schema/index.h` - Index support
- `dev/schema/triggers.h` - Trigger support

**Query building:**
- `dev/conditions.h` - WHERE clause conditions (41K+ lines)
- `dev/core_functions.h` - SQL functions (82K+ lines)
- `dev/select_constraints.h` - SELECT modifiers (ORDER BY, LIMIT, etc.)
- `dev/ast/` - AST nodes for complex SQL constructs

**Type binding:**
- `dev/statement_binder.h` - Binds C++ values to prepared statements
- `dev/row_extractor.h` - Extracts C++ objects from result rows
- `dev/field_printer.h` - Serializes field values

**Utilities:**
- `dev/prepared_statement.h` - Prepared statement support
- `dev/ast_iterator.h` - Traverses expression ASTs
- `dev/transaction_guard.h` - RAII transaction guards
- `dev/cte_storage.h` - Common Table Expression support

### Header Organization
The library has ~22,500 lines across the `dev/` directory headers. The main header `include/sqlite_orm/sqlite_orm.h` includes all necessary components. The `dev/` directory contains the actual implementation, organized by functionality.

## Development Workflow

### Code Style
- Follow existing code patterns in the codebase
- C++17 is the baseline; conditional compilation for C++20/23/26 features
- Use template metaprogramming for compile-time type safety
- Member pointers are used extensively for column mapping

### Testing Requirements
- All changes must include tests in the `tests/` directory
- Use Test-Driven Development (TDD): write failing test first
- Tests use Catch2 framework with `TEST_CASE` and `SECTION`
- Ensure tests pass on multiple platforms (CI runs on Linux, Windows, macOS)

### Compilation Considerations
- MSVC requires `/bigobj` flag for 64-bit builds (already configured)
- MSVC requires `/EHsc` for exception handling
- Clang/GCC: watch for `-Wreorder` warnings (treated as errors)
- Large template instantiations may cause long compile times

### Pull Request Guidelines
Per `CONTRIBUTING.md`:
- Create GitHub issue for significant changes (not needed for typos/warnings)
- PR title must begin with issue number: `#9999 : description`
- Base PRs against `dev` branch (not `master`)
- Commit messages in English only
- Squash commits if adding/removing code within same PR
- All tests must pass on CI (Travis, AppVeyor, GitHub Actions)

## Common Patterns

### Creating a storage
```cpp
auto storage = make_storage("database.db",
make_table("users",
make_column("id", &User::id, primary_key().autoincrement()),
make_column("name", &User::name)
)
);
storage.sync_schema(); // Creates/migrates schema
```

### Query expressions
Queries are built using expression objects that overload operators:
- `where(c(&User::id) == 5)` - type-safe WHERE clause
- `order_by(&User::name)` - ORDER BY
- `limit(10)` - LIMIT
- Composable: `where(...), order_by(...), limit(...)`

### Member pointers for type safety
`&User::id` is used instead of string `"id"`. The library deduces the type and column name from the member pointer at compile time, preventing runtime SQL injection and type mismatches.

## Important Notes

- **Thread safety**: See `docs/thread-safety.md`. Storage objects are thread-safe by default since v1.10.
- **Schema sync**: `sync_schema()` attempts to preserve data but may drop tables if column constraints change significantly. Back up data before schema changes.
- **In-memory databases**: Use `:memory:` or `""` as filename.
- **Primary key requirement**: CRUD operations like `get()`, `update()`, `remove()` require a primary key column.
- **No raw SQL**: The library's design philosophy avoids raw SQL strings for type safety, but you can use `execute()` for raw queries if needed.

## Current Branch
The current branch is `experimental/sql-view` (based on `dev`). Main branch is `master`, but active development happens on `dev`.
3 changes: 2 additions & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ version: "{build}"
skip_branch_with_pr: true
skip_commits:
files:
- .git*
- .git/*
- .claude/*
- .travis.yml
- _config.yml
- LICENSE
Expand Down
163 changes: 163 additions & 0 deletions third_party/fix_comment_alignment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#!/usr/bin/env python3
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this file?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually don't need it. I used it to reformat the comments after collapsing the namespaces, because clang-format didn't do it. I just thought I add it because it required a few iterations to instruct Claude to produce something that works. If you think it is unnecessary I can remove it, so we don't keep dead freight :)

"""
Fix multi-line comment alignment in sqlite_orm dev/ headers.

For a comment opening at column N:
- Continuation lines with `*` should have `*` at column N+1
- Text on continuation lines should start at the next multiple of 4 from column 0
- Continuation lines without `*` get an asterisk added
- The closing `*/` should be at column N+1

This script adds asterisks to continuation lines that don't have them, making all comments consistent.
"""

import re
from pathlib import Path


def next_multiple_of_4(n):
"""Return the next multiple of 4 that is greater than n."""
return ((n // 4) + 1) * 4


def fix_comment_alignment(file_path):
"""Fix comment alignment in a single file."""
with open(file_path, 'r', encoding='utf-8') as f:
lines = f.readlines()

modified = False
result_lines = []
in_comment = False
comment_start_col = 0

i = 0
while i < len(lines):
line = lines[i]

# Check if we're starting a multi-line comment
if not in_comment:
# Look for /* or /** at the start of a line (after whitespace)
match = re.match(r'^(\s*)/(\*+)(.*)$', line)
if match:
leading_spaces = match.group(1)
stars = match.group(2)
rest = match.group(3)

# Check if it's a single-line comment
if rest.rstrip().endswith('*/'):
result_lines.append(line)
i += 1
continue

# Multi-line comment starts
in_comment = True
comment_start_col = len(leading_spaces)
result_lines.append(line)
i += 1
continue

# If we're in a comment, process continuation and closing lines
if in_comment:
# Expected indentation for continuation lines
star_col = comment_start_col + 1 # Where * should be
text_col = next_multiple_of_4(star_col) # Where text should start (next multiple of 4)

# Check if this is the closing line (just spaces followed by */)
if re.match(r'^\s*\*/$', line):
current_indent = len(line) - len(line.lstrip())
if current_indent != star_col:
fixed_line = ' ' * star_col + '*/\n'
result_lines.append(fixed_line)
modified = True
else:
result_lines.append(line)
in_comment = False
i += 1
continue

# Check for continuation line starting with *
match = re.match(r'^(\s+)\*\s*(.*)$', line)
if match:
current_indent = len(match.group(1))
text_content = match.group(2)

# Calculate expected format
spaces_after_star = text_col - star_col - 1 # -1 for the asterisk itself
expected_line = ' ' * star_col + '*' + ' ' * spaces_after_star + text_content + '\n'

# Check if current line matches expected format
if line != expected_line:
result_lines.append(expected_line)
modified = True
else:
result_lines.append(line)

# Check if this line contains the closing */
if text_content.rstrip().endswith('/'):
in_comment = False
i += 1
continue

# Continuation line without asterisk
# Add asterisk and align properly, but skip preprocessor directives
if line.lstrip().startswith('#'):
# Preprocessor directive - this ends the comment
result_lines.append(line)
in_comment = False
i += 1
continue

if line.strip(): # Non-empty line
content = line.lstrip()
# Add asterisk and proper spacing
spaces_after_star = text_col - star_col - 1
fixed_line = ' ' * star_col + '*' + ' ' * spaces_after_star + content
result_lines.append(fixed_line)
modified = True
else:
# Empty line in comment - keep as is
result_lines.append(line)

i += 1
continue

# Normal line (not in comment)
result_lines.append(line)
i += 1

if modified:
new_content = ''.join(result_lines)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
return True
return False


def main():
"""Process all .h files in dev/ directory."""
dev_dir = Path('dev')

if not dev_dir.exists():
print(f"Error: {dev_dir} directory not found")
return

modified_files = []

# Find all .h files recursively
for h_file in sorted(dev_dir.rglob('*.h')):
if fix_comment_alignment(h_file):
modified_files.append(str(h_file))
print(f"Fixed: {h_file}")

print(f"\n{'='*60}")
print(f"Total files modified: {len(modified_files)}")
print(f"{'='*60}")

if modified_files:
print("\nModified files:")
for f in modified_files:
print(f" - {f}")


if __name__ == '__main__':
main()