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
81 changes: 81 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# phenixphp/sqlite - AI Coding Instructions

Asynchronous SQLite client for PHP built on Amp v3 framework. This library adapts patterns from `amphp/mysql` to work with SQLite's file-based architecture.

## Architecture Overview

**Core Components:**
- `SqliteConnection` - Main connection abstraction wrapping `Internal\ConnectionProcessor`
- `SqliteConfig` - File-based configuration (path, open flags, pragmas) instead of network config
- `SqliteColumnDefinition` - Simplified metadata reflecting SQLite's 5 storage classes (NULL, INTEGER, REAL, TEXT, BLOB)
- `SqliteDataType` enum - Type affinity system, NOT MySQL's 30+ types

**Key Design Decisions:**
- SQLite uses **storage classes** not strict types - see `SqliteDataType::fromDeclaredType()` for affinity rules
- No connection pooling needed (file-based, not network) - connection reuse pattern differs from MySQL
- Uses Amp's parallel worker pools for potential multi-connection scenarios (see `amphp/parallel` in `knowledge/`)
- Adapting MySQL protocol patterns to SQLite3 native extensions
- Don't act condescendingly; adopt a technical, critical, and punctual approach.

## Critical Workflows

```bash
# Format code (uses PSR-12 + custom rules)
composer format

# Static analysis (PHPStan level max)
composer analyze

# Run tests (uses Pest, not PHPUnit)
composer test
composer test:parallel # Uses parallel execution
```

## Code Conventions

**Type Declarations:**
- ALWAYS `Type|null` NEVER `?Type` (enforced by PHP-CS-Fixer `nullable_type_declaration`)
- Blank line after `<?php` before `declare(strict_types=1);`
- No `final` keyword on classes (project standard)
- DocBlocks only when PHPStan requires them

**SQLite-Specific:**
- Type affinity over strict types - `SqliteDataType::Blob` for empty declared types
- `getLastInsertId()` returns `int|null` (supports RETURNING but maintains traditional pattern)
- No charset/collation config (UTF-8 default)
- Configuration uses `path`, `openFlags`, `busyTimeout`, `journalMode`, `synchronous`, `foreignKeys`, `cacheSize`

**Amp Patterns:**
```php
// NO async/await keywords in PHP - use Amp primitives
use function Amp\async;
use function Amp\delay;

$future = async(fn() => $connection->query($sql));
$result = $future->await();
```

## Reference Implementation

Look at `knowledge/amphp-mysql/` for database patterns adapted to SQLite:
- Connection lifecycle management
- Prepared statements with parameter binding
- Result set handling with column definitions
- Transaction isolation levels

Key differences from MySQL:
- No `host`, `port`, `user`, `password` in config
- No compression or network-related flags
- Simpler column metadata (no charset field)
- Different PRAGMA-based configuration vs SQL variables

## Integration Points

- Extends `Amp\Sql\SqlConnection` interface
- Uses `Amp\Parallel\Worker` for potential concurrent file operations
- Leverages `Amp\Parser\Parser` for protocol handling (adapted from MySQL patterns)
- File operations via Amp event loop, not blocking I/O

## Testing

Uses **Pest** (not PHPUnit) - see `composer.json` scripts. Test structure follows Amp conventions with async test cases.
21 changes: 21 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) Omar Barbosa

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
240 changes: 240 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,242 @@
# phenixphp/sqlite

<p>Asynchronous SQLite 3 client for PHP based on <a href="https://amphp.org/">Amp</a>.</p>

[![Tests](https://github.com/phenixphp/sqlite/actions/workflows/run-tests.yml/badge.svg?branch=main)](https://github.com/phenixphp/sqlite/actions/workflows/run-tests.yml)
[![Latest Version on Packagist](https://img.shields.io/packagist/v/phenixphp/sqlite.svg?style=flat-square)](https://packagist.org/packages/phenixphp/sqlite)
[![Total Downloads](https://img.shields.io/packagist/dt/phenixphp/sqlite.svg?style=flat-square)](https://packagist.org/packages/phenixphp/sqlite)
[![PHP Version](https://img.shields.io/packagist/php/phenixphp/sqlite.svg?style=flat-square)](https://packagist.org/packages/phenixphp/sqlite)
[![License](https://img.shields.io/packagist/license/phenixphp/sqlite.svg?style=flat-square)](https://packagist.org/packages/phenixphp/sqlite)

---

**phenixphp/sqlite** is part of the **Phenix PHP** framework ecosystem. Phenix is a web framework built on pure PHP, without external extensions, based on the [Amphp](https://amphp.org/) ecosystem, which provides non-blocking operations, asynchronism and parallel code execution natively. It runs in the PHP SAPI CLI and on its own server — it is simply powerful.

---

## Table of Contents

- [Requirements](#requirements)
- [Installation](#installation)
- [Configuration](#configuration)
- [Usage](#usage)
- [Establishing a Connection](#establishing-a-connection)
- [Executing Queries](#executing-queries)
- [Prepared Statements](#prepared-statements)
- [Transactions](#transactions)
- [Dependencies](#dependencies)
- [License](#license)

---

## Requirements

| Requirement | Version |
| --- | --- |
| PHP | `^8.2` |
| ext-sqlite3 | `*` |

---

## Installation

Install the package via [Composer](https://getcomposer.org/):

```bash
composer require phenixphp/sqlite
```

---

## Configuration

The `SqliteConfig` class is the entry point for configuring the SQLite connection. It allows you to define the database path and connection parameters aligned with the Amphp SQL ecosystem.

```php
<?php

use Phenix\Sqlite\SqliteConfig;

$config = new SqliteConfig(
database: __DIR__ . '/database.db',
);
```

> You can use `:memory:` as the database path to create an in-memory SQLite database, which is ideal for testing scenarios.

```php
$config = new SqliteConfig(
database: ':memory:',
);
```

---

## Usage

### Establishing a Connection

Use `SqliteConfig` to create and manage asynchronous connections to your SQLite database. Since this package is built on top of Amphp, all database operations are **non-blocking** and run asynchronously using fibers.

```php
<?php

declare(strict_types=1);

use Phenix\Sqlite\SqliteConfig;

use function Phenix\Sqlite\connect;

$config = new SqliteConfig(
database: __DIR__ . '/database.db',
);

$connection = connect($config);

// ... use connection ...

$connection->close();
```

---

### Executing Queries

Once you have an active connection, you can execute SQL statements — `CREATE`, `INSERT`, `SELECT`, `UPDATE`, and `DELETE` — all asynchronously without blocking the event loop.

#### Creating a Table

```php
$connection->execute(
'CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL
)'
);
```

#### Inserting Records

```php
$connection->execute(
"INSERT INTO users (name, email) VALUES ('John Doe', 'john@example.com')"
);
```

#### Selecting Records

```php
$result = $connection->query('SELECT * FROM users');

foreach ($result as $row) {
echo $row['id'] . ': ' . $row['name'] . ' — ' . $row['email'] . PHP_EOL;
}
```

#### Updating Records

```php
$connection->execute(
"UPDATE users SET name = 'Jane Doe' WHERE id = 1"
);
```

#### Deleting Records

```php
$connection->execute(
"DELETE FROM users WHERE id = 1"
);
```

---

### Prepared Statements

Prepared statements allow you to precompile a SQL template and execute it repeatedly with different bound parameters. This improves both performance and security by preventing SQL injection.

#### Preparing and Executing a Statement

```php
$statement = $connection->prepare(
'INSERT INTO users (name, email) VALUES (?, ?)'
);

$statement->execute(['Alice', 'alice@example.com']);
$statement->execute(['Bob', 'bob@example.com']);
```

#### Prepared Statement with Named Parameters

```php
$statement = $connection->prepare(
'SELECT * FROM users WHERE name = ? AND email = ?'
);

$result = $statement->execute(['Alice', 'alice@example.com']);

foreach ($result as $row) {
echo $row['name'] . PHP_EOL;
}
```

#### Closing a Prepared Statement

```php
$statement->close();
```

---

### Transactions

Transactions group multiple SQL operations into a single atomic unit. If any operation fails, all changes within the transaction are rolled back, ensuring data consistency.

#### Basic Transaction

```php
$transaction = $connection->beginTransaction();

try {
$transaction->execute(
"INSERT INTO users (name, email) VALUES ('Charlie', 'charlie@example.com')"
);
$transaction->execute(
"INSERT INTO users (name, email) VALUES ('Diana', 'diana@example.com')"
);

$transaction->commit();
} catch (\Throwable $e) {
$transaction->rollback();

throw $e;
}
```

#### Transaction with Prepared Statements

```php
$transaction = $connection->beginTransaction();

try {
$statement = $transaction->prepare(
'INSERT INTO users (name, email) VALUES (?, ?)'
);

$statement->execute(['Eve', 'eve@example.com']);
$statement->execute(['Frank', 'frank@example.com']);

$transaction->commit();
} catch (\Throwable $e) {
$transaction->rollback();

throw $e;
}
```

---

## License

This package is open-sourced software licensed under the [MIT](https://opensource.org/licenses/MIT) license.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
],
"require": {
"php": "^8.2",
"ext-sqlite3": "*",
"amphp/amp": "^3",
"amphp/parser": "^1.1",
"amphp/pipeline": "^1",
Expand Down