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
2 changes: 2 additions & 0 deletions .idea/sqldialects.xml

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

52 changes: 43 additions & 9 deletions docs/datetime.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ Generally, we recognize two types of date-time types:

The following table presents a matrix of available DB date-time types:

| | Local DateTime<br>no timezone handling | DateTime<br>timezone conversion | DateTime<br>timezone stored |
|-------------|-----------------------------------------|---------------------------------|-----------------------------|
| MySQL | `datetime` | `timestamp` | - |
| Postgres | `timestamp` | `timestamptz` | - |
| SQL Server | `datetime`, `datetime2` | - | `datetimeoffset` |

| | Local DateTime<br>no timezone handling | DateTime<br>timezone conversion | DateTime<br>timezone stored |
|------------|----------------------------------------|---------------------------------|-----------------------------|
| MySQL | `datetime` | `timestamp` | - |
| Postgres | `timestamp` | `timestamptz` | - |
| SQL Server | `datetime`, `datetime2` | - | `datetimeoffset` |
| Sqlite | - | - | - |
-
- **no timezone handling**: database stores the time-stamp and does not do any modification to it; this is the easiest solution, but brings a disadvantage: database cannot exactly diff two time-stamps, i.e. it may produce wrong results because day-light saving shift is needed but db does not know which zone to use for the calculation;
- **timezone conversion**: database stores the time-stamp unified in UTC and reads it in connection's timezone;
- **timezone stored**: database does not do any conversion, it just stores the timezoned timestamp and returns it back;
Expand All @@ -25,10 +26,10 @@ Dbal offers a **connection time zone** configuration option (`connectionTz`) tha

Dbal comes with two query modifiers:

| Type | Modifier | Description |
|----------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------|
| Type | Modifier | Description |
|----------------|----------|--------------------------------------------------------------------------------------------------------------------------------|
| local datetime | `%ldt` | passes DateTime(Interface) object as it is, without any timezone conversion and identification; formerly known as datetime simple (`%dts`) |
| datetime | `%dt` | converts DateTime(Interface) object to connection timezone; |
| datetime | `%dt` | converts DateTime(Interface) object to connection timezone; |

---------------

Expand Down Expand Up @@ -90,3 +91,36 @@ This will make Dbal fully functional, although some SQL queries and expressions
|----------------|------------------|--------------------------------------------------------------------------------------------------------------------------|
| local datetime | `datetime` | value is converted into application timezone |
| datetime | `datetimeoffset` | value is read with timezone offset and no further modification is done - i.e. no application timezone conversion happens |

--------------------------

### Sqlite

Sqlite does not have dedicated date/time storage types. Dbal therefore relies on the declared column type and uses a convention for exact timestamps.

Use `datetime(your_column, 'unixepoch', 'localtime')` to convert stored timestamp to your local time-zone. Read more in the [official documentation](https://sqlite.org/lang_datefunc.html#modifiers).

##### Writing

| Type | Modifier | Comment |
|----------------|----------|-------------------------------------------------------------------------------------------------|
| local datetime | `%ldt` | the timezone offset is removed and value is formatted as ISO string without the timezone offset |
| datetime | `%dt` | value is converted to connection timezone and stored as unix timestamp in milliseconds |

##### Reading

| Type | Declared Column Type | Comment |
|----------------|------------------------------------------------------------------------------|-------------------------------------------------------------------|
| local datetime | `date`, `datetime`, `time` | built-in aliases supported by the SQLite driver |
| local datetime | `localdate`, `localdatetime`, `localtime` | short aliases if you want to distinguish intent explicitly |
| local datetime | `dbal_local_date`, `dbal_local_datetime`, `dbal_local_time` | recommended explicit Dbal convention for Sqlite schemas |
| datetime | `timestamp`, `unixtimestamp`, `dbal_timestamp` | interpreted as unix timestamp in milliseconds and converted to app timezone |

##### Detection Notes

- Sqlite detection is based on the declared column type returned by PDO metadata.
- `dbal_timestamp` is the recommended type name for exact timestamps stored as unix milliseconds.
- `dbal_local_date`, `dbal_local_datetime`, and `dbal_local_time` are the recommended type names for local values stored as strings.
- `dbal_bool` is supported as an explicit boolean declared type.
- Other supported scalar type aliases are standard SQL-style names such as `int`, `integer`, `tinyint`, `smallint`, `bigint`, `real`, `float`, `numeric`, and `decimal`.
- If you use unrecognized custom type names, Dbal will not auto-normalize them.
25 changes: 13 additions & 12 deletions docs/default.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,24 @@ Supported platforms:

- **MySQL** via `mysqli` or `pdo_mysql` extension,
- **Postgres** via `pgsql` or `pdo_pgsql` extension,
- **MS SQL Server** via `sqlsrv` or `pdo_sqlsrv` extension.
- **MS SQL Server** via `sqlsrv` or `pdo_sqlsrv` extension,
- **Sqlite** via `pdo_sqlite` extension.

### Connection

The Connection instance is the main access point to the database. Connection's constructor accepts a configuration array. The possible keys depend on the specific driver; some configuration keys are shared for all drivers. To actual list of supported keys are enumerated in PhpDoc comment in driver's source code.

| Key | Description |
|-----------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
| `driver` | driver name, use `mysqli`, `pgsql`, `sqlsrv`, `pdo_mysql`, `pdo_pgsql`, `pdo_sqlsrv` |
| `host` | database server name |
| `port` | database server port |
| `username` | username for authentication |
| `password` | password for authentication |
| `database` | database name |
| `charset` | charset encoding of the connection |
| `nestedTransactionsWithSavepoint` | boolean which indicates whether use save-points for nested transactions; `true` by default |
| `sqlProcessorFactory` | factory implementing ISqlProcessorFactory interface; use for adding custom modifiers; `null` by default; |
| Key | Description |
|-----------------------------------|-----------------------------------------------------------------------------------------------------------------------|
| `driver` | driver name, use `mysqli`, `pgsql`, `sqlsrv`, `pdo_mysql`, `pdo_pgsql`, `pdo_sqlsrv`, or `pdo_sqlite` |
| `host` | database server name |
| `port` | database server port |
| `username` | username for authentication |
| `password` | password for authentication |
| `database` | database name |
| `charset` | charset encoding of the connection |
| `nestedTransactionsWithSavepoint` | boolean which indicates whether use save-points for nested transactions; `true` by default |
| `sqlProcessorFactory` | factory implementing ISqlProcessorFactory interface; use for adding custom modifiers; `null` by default; |
| `connectionTz` | timezone for the connection; pass a timezone name, `auto` or `auto-offset` keyword, see [DateTime TimeZones](datetime) chapter for more info; |
| `searchPath` | *PgSQL only*; sets the connection `search_path`; |
| `sqlMode` | *MySQL only*; sets the `sql_mode`, `TRADITIONAL` by default; |
Expand Down
1 change: 1 addition & 0 deletions docs/menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- [Config with Symfony](config-symfony)
- [Modifiers](param-modifiers)
- [Result](result)
- [Result Normalization](result-normalization)
- [Query Builder](query-builder)
- [DateTime](datetime)

Expand Down
95 changes: 95 additions & 0 deletions docs/result-normalization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
## Result Normalization

Dbal automatically normalizes selected result values to PHP types based on driver metadata.

Normalization is enabled by default for every `Nextras\Dbal\Result\Result` instance returned from `Connection::query()`.

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

$result->setValueNormalization(false); // return raw driver values
$result->setValueNormalization(true); // restore default driver normalization
```

Normalization is decided per column from metadata reported by the active driver. Dbal does not inspect SQL expressions semantically; it relies on the type information the extension exposes for each selected column. That metadata is not equally detailed across all drivers, and for some queries it may be incomplete or missing entirely.

If a type is not recognized, Dbal leaves the value unchanged and returns the original driver value. This raw value can differ not only between drivers, but sometimes even between different PHP versions of the same driver, because native extensions and PDO metadata handling can change over time.

### General Rules

- Integers are normalized to `int`.
- Floating-point values are normalized to `float` where the driver metadata clearly marks them as floating-point.
- Date and time values are normalized to `Nextras\Dbal\Utils\DateTimeImmutable`.

### MySQL

Drivers: `mysqli`, `pdo_mysql`.

Notes:
- Decimal values are left as strings.
- `timestamp` is treated as an exact timestamp.
- `datetime` and `date` are treated as local values.

| Column Type in DB | PHP Type |
|----------------------------------------------------|----------------------------------------|
| `BIT`, `TINY`, `SHORT`, `LONG`, `LONGLONG`, `YEAR` | `int` |
| `FLOAT`, `DOUBLE` | `float` |
| `datetime`, `date` | `Nextras\Dbal\Utils\DateTimeImmutable` |
| `timestamp` | `Nextras\Dbal\Utils\DateTimeImmutable` |
| `time` | `DateInterval` |

### PostgreSQL

Drivers: `pgsql`, `pdo_pgsql`.

Notes:
- `numeric` is left as string.
- PostgreSQL date/time values are parsed from textual representation and then converted to the application timezone.
- `pdo_pgsql` leaves `bool` values untouched because PDO already returns a suitable scalar value.

| Column Type in DB | PHP Type |
|------------------------------------------------------|----------------------------------------|
| `int2`, `int4`, `int8` | `int` |
| `float4`, `float8` | `float` |
| `bool` (`pgsql`) | `bool` |
| `date`, `time`, `timestamp`, `timetz`, `timestamptz` | `Nextras\Dbal\Utils\DateTimeImmutable` |
| `interval` | `DateInterval` |
| `bit`, `varbit` | `int` |
| `bytea` | `string` |

### SQL Server

Drivers: `sqlsrv`, `pdo_sqlsrv`.

Notes:
- `datetimeoffset` keeps the stored offset semantics.
- Decimal and money-like values are left as strings.

| Column Type in DB | PHP Type |
|----------------------------------------------------------|----------------------------------------|
| integer types | `int` |
| `real` | `float` |
| `bit` | `bool` |
| `date`, `time`, `datetime`, `datetime2`, `smalldatetime` | `Nextras\Dbal\Utils\DateTimeImmutable` |
| `datetimeoffset` | `Nextras\Dbal\Utils\DateTimeImmutable` |

### Sqlite

Driver: `pdo_sqlite`.

Notes:
- SQLite normalization depends on the declared column type name.
- Local datetime aliases are parsed as local values.
- Datetime aliases are interpreted as unix timestamps in milliseconds.
- `%dt` writes unix timestamps in milliseconds.
- `%ldt` writes local string values without timezone offset.
- If you use an unrecognized custom declared type, Dbal leaves the value unchanged.
- Generic SQLite `text` and `varchar` columns are not auto-normalized.

| Column Type in DB | PHP Type |
|------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------|
| `int`, `integer`, `tinyint`, `smallint`, `mediumint`, `bigint`, `unsigned big int`, `int2`, `int8` | `int` |
| `real`, `double`, `double precision`, `float`, `numeric`, `decimal` | `float` |
| `bool`, `boolean`, `bit`, `dbal_bool` | `bool` |
| `date`, `datetime`, `time`, `localdate`, `localdatetime`, `localtime`, `dbal_local_date`, `dbal_local_datetime`, `dbal_local_time` | `Nextras\Dbal\Utils\DateTimeImmutable` |
| `timestamp`, `unixtimestamp`, `dbal_timestamp` | `Nextras\Dbal\Utils\DateTimeImmutable` |
12 changes: 12 additions & 0 deletions docs/result.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,15 @@ $result->unbuffered(); // disable the emulated buffering
```

If the unbuffered Result was already partially consumed, enabling buffering does nothing and Result will potentially throw an exception when rewinded or seeked. If the buffered Result was already partially consumed, disabling buffering does nothing and Result will still use the buffer.

### Value Normalization

Dbal automatically normalizes selected column values to PHP types based on driver metadata. You can disable or re-enable that behavior per result:

```php
$result = $connection->query('SELECT * FROM events');
$result->setValueNormalization(false);
$result->setValueNormalization(true);
```

See the [Result Normalization](result-normalization) chapter for the exact behavior of each driver.
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Supported platforms:
- **MySQL** via `mysqli` or `pdo_mysql` extension,
- **PostgreSQL** via `pgsql` or `pdo_pgsql` extension,
- **MS SQL Server** via `sqlsrv` or `pdo_sqlsrv` extension.
- **Sqlite** via `pdo_sqlite` extension.

Integrations:
- Symfony Bundle
Expand Down
Loading
Loading