Skip to content

Commit

Permalink
doc enhancements (datetime & typos)
Browse files Browse the repository at this point in the history
  • Loading branch information
hrach committed Mar 28, 2021
1 parent 6055976 commit 3c2043c
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 60 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug-report.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: "\U0001F41B Bug report"
labels: "bug"
about: Report if something isn't working as expected.
about: Report if something is not working as expected.

---

Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/question.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: "\U0001F450 Question"
labels: "question"
about: Any question? Don't hesitate and ask.
about: Any question? Do not hesitate and ask.

---

Expand Down
95 changes: 62 additions & 33 deletions doc/datetime.texy
Original file line number Diff line number Diff line change
@@ -1,55 +1,84 @@
DateTime TimeZones Support
##########################
DateTime
########

Database engines provide different types for storing date-times. Also, the type naming is often misleading. This documentation page covers the basics and Dbal's solution to the datetime & timezone problem.
Database engines provide different types for storing date-times. Also, the type naming is often misleading. This documentation chapter covers the basics and Dbal's solution to the datetime & timezone handling.

Generally, we recognize three types of date time types:
Generally, we recognize two types of date-time types:

- **Local DateTime** - it is a date time which has not an exact position on the *time-line*; simply, we do not know in which time zone the event happened, therefore we consider the information as a local; example: date time when the school year begins, since the country may be across more the timezones, this type of information may be stored as a local date time, i.e. striping the "exactness" may be an advantage here;
- **UTC DateTime** - is an exact timestamp on the *time-line*; example: timestamp of the meeting start in a calendar;
- **Zoned DateTime** - is an exact timestamp on the *time-line* plus an added context of specific time-zone; either we use reader's timezone or timezone of the location when the timestamp "happened"; example: presenting an online streaming event start - since it is pretty usual that this event will be watched from multiple times, we need add reader's timezone context to the *stored* UTC DateTime.
- **DateTime** - is an exact timestamp on the *time-line*; example: timestamp of the meeting start in a calendar; this type is also referred as an `Instant` type.
- **UTC DateTime** - having this type represented in UTC means we don't know an exact context where it happened; it could be in the day or in the night;
- **Zoned DateTime** - is an exact timestamp on the *time-line* plus an additional context of specific time-zone; either we use reader's timezone or timezone of the location where the timestamp "happened"; example: presenting an online streaming event start - since it is pretty usual that this event will be watched from multiple places, we need add reader's timezone context to the *stored* UTC DateTime.

The following table presents a matrix of available data-time types and their behavior:
The following table presents a matrix of available DB date-time types:

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

- **no timezone handling**: this is straightforward attitude; database just store your time-stamp and does not do any modification to the timestamp; this is the easiest solution, but brings a disadvantage: database cannot exactly diff two timestamps, i.e. it maz produce wrong results because day-light saving shift`
- **timezone conversion**: database stores the time stamp unified in UTC and for MySQL, it converts the timestamp to connection's timezone for every read and write;
- **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;

Dbal offers **connection time zone** configuration option (`connectionTz`) which defines the timezone for database connection communication; by default it equals to PHP's current default timezone. This option is configured by timezone name, e.g. `Europe/Prague` string.
Dbal offers a **connection time zone** configuration option (`connectionTz`) that defines the timezone for database connection communication; it equals to PHP's current default timezone by default. This option is configured by a timezone name, e.g. `Europe/Prague` string.

/--div .[note]
By default, MySQL server doesn't support named timezones, see [the setup chapter | timezones-mysql-support] how to configure them. Still, there is a possibility to pass only a timezone offset, e.g. `+03:00`, but this is not ideal. Use magic `auto-offset` value that will be dynamically converted to the current PHP's timezone offset.
Dbal comes with two query modifiers:

This will make Dbal fully functional, although some SQL queries and expressions may not return correctly calculated results, e.g. functions that calculate two-date operations directly in the database - `TIMEDIFF`, `ADDDATE`, etc.
\--
|* localdatetime | `%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;

Storing
=======
--------------------------

Dbal comes with two query modifiers:
- **`%dt`** (as datetime): converts DateTime object to connection timezone;
- **`%ldt`** (as local datetime): pass DateTime object as it is, without any timezone conversion and identification; formerly known as datetime simple (%dts);
MySQL
*****

|------------------------------------------------------------------------------
| | Local DateTime | UTC DateTime | Zoned DateTime
| | no timezone handling | timezone conversion | timezone stored
|------------------------------------------------------------------------------
| MySQL | `%ldt` | `%dt` | -
| Postgres | `%ldt` | `%dt` | -
| SQL Server | `%ldt` | - | `%dt`
.[fixed]
|* Writing ||
|* local datetime |* datetime
| `%ldt` modifier | `%dt` modifier
| timezone (offset) is removed | value is converted to connection timezone and timezone offset is removed if properly stored to `timestamp` column type
|* Reading ||
|* local datetime |* datetime
| `datetime` column type | `timestamp` column type
| value is converted into application timezone | value is interpreted in connection timezone and converted into application timezone

Connection Time Zone
--------------------

By default, MySQL server does not support named timezones, see [the setup chapter | timezones-mysql-support] how to configure them. Still, there is a possibility to pass only a timezone offset configuraion, e.g. `+03:00`, but this is not ideal. Use rather magic `auto-offset` value that will be dynamically converted to the current PHP's timezone offset.

This will make Dbal fully functional, although some SQL queries and expressions may not return correctly calculated results, e.g. functions calculating two-date operations directly in the database - `TIMEDIFF`, `ADDDATE`.

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

Postgres
********

.[fixed]
|* Writing ||
|* local datetime |* datetime
| `%ldt` modifier | `%dt` modifier
| timezone (offset) is removed | value is converted to connection timezone and timezone offset is removed if properly stored to `timestamptz` column type.
|* Reading ||
|* local datetime |* datetime
| `timestamp` column type | `timestamptz` column type
| value is converted into application timezone | value is converted into application timezone

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

Reading
=======
SQL Server
**********

Database drivers also correctly read the stored values and convert them into `DateTimeImmutable` instances:
- **MySQL** driver interprets `timestamp` column in connection's timezone and converts it to PHP's default timezone;
- **Postgres** driver reads `timestamptz` (timezoned timestamp) and converts it to PHP's default timezone;
- **SQL server** driver reads `datetimeoffset` (timezoned timestamp) and does not do any timezone conversion;
.[fixed]
|* Writing ||
|* local datetime |* datetime
| `%ldt` modifier | `%dt` modifier
| timezone (offset) is removed | no timezone conversion is done and the timezone offset is stored in `datetimeoffset` db type
|* Reading ||
|* local datetime |* datetime
| `datetime` or `datetime2` column type | `datetimeoffset` column type
| value is converted into application timezone | value is read with timezone offset and no further modification is done - i.e. no application timezone conversion happens
26 changes: 13 additions & 13 deletions doc/default.texy
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Nextras Dbal
############

A powerful abstraction layer for database. **Fast & Save**.
Dbal is concise and secure API to construct queries and fetch data from storage independently on the database engine.

Supported platforms:

Expand All @@ -12,17 +12,17 @@ Supported platforms:
Connection
==========

The connection instance is a main object that provides an API for accessing your database. Connection's constructor accepts a configuration array. The possible keys depend on the specific driver; some configuration keys are the shared for all drivers:
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 is enumerated in PhpDoc comment in driver's source code.

|* driver | driver name, use `mysqli`, `pgsql`, `sqlsrv`, `pdo_mysql`, `pdo_pgsql`, `pdo_sqlsrv`
|* PHP's driver | driver name, use `mysqli`, `pgsql`, `sqlsrv`, `pdo_mysql`, `pdo_pgsql`, `pdo_sqlsrv`
|* host | database server name
|* username | username for authentication
|* password | password for authentication
|* database | name of the database
|* charset | charset encoding of the connection
|* connectionTz | timezone for the connection; pass a timezone name, `auto` or `auto-offset` keyword, see [DateTime TimeZones | datetime] chapter for more info;
|* 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;
|* ssl* | *MySQL only*; use `sslKey`, `sslCert`, `sslCa`, `sslCapath` and `sslCipher` to set SSL options for connection;
Expand All @@ -37,14 +37,14 @@ $connection = new Nextras\Dbal\Connection([
]);
\--

The connection implementation is lazy; it connects to database when needed. You can explicetely connect by calling `connect()` method; you can also `disconnect()` or `reconnect()` your connection. Use `ping()` method to stay in touch.
The Connection's implementation is lazy; it connects to database only when needed. You can explicitly connect by calling `connect()` method; you can also `disconnect()` or `reconnect()` the connection. Use `ping()` method to avoid connection timeouts.

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

Querying
========

Use `query()` method to run SQL queries. Query method accepts a single SQL statement. Dbal supports parameter placeholders called modifiers - values are passed separately and its value will replace the placeholder properly escaped and sanitized. Read more in [Parameter Modifiers| param-modifiers] chapter.
Use `query()` method to run SQL queries. The query method accepts a single SQL statement. Dbal supports parameter placeholders called modifiers - values are passed separately and its value will replace the placeholder with properly escaped and sanitized value. Read more in [Parameter Modifiers| param-modifiers] chapter.

/--php
$connection->query('SELECT * FROM foo WHERE id = %i', 1);
Expand All @@ -54,16 +54,16 @@ $connection->query('SELECT * FROM foo WHERE title = %s', 'foo" OR 1=1');
// SELECT * FROM foo WHERE title = "foo\" OR 1=1"
\--

Our SQL processor supports `[]` (square brackets) for easily escaping of column/table names. However, if you pass a column name as an input retrieved from an user, use the proper `%column` modifier.
Our SQL processor supports `[]` (square brackets) for easily escaping of column/table names. However, if you want to pass a input retrieved from an user as a column name, use the save `%column` modifier.

/--php
$connection->query('SELECT * FROM [foo] WHERE %column = %i', 'id', 1);
// SELECT * FROM `foo` WHERE `id` = 1
\--

To retrieve the last inserted id use `getLastInsertedId()` method. For PostgreSQL the method accepts a sequence name. The number of affected rows is available through `getAffectedRows()` method.
To retrieve the last inserted id, use `getLastInsertedId()` method, it accepts a sequence name for PostgreSQL. The number of affected rows is available through `getAffectedRows()` method.

Each `query()` returns new `Nextras\Dbal\Result\Result` object. Result object allows to iterate over the fetched data and fetch each row into `Nextras\Dbal\Result\Row` instance. `Row` instance is a simple value object with property access:
Each `query()` returns new `Nextras\Dbal\Result\Result` instance. Result's instance allows to iterate over the fetched rows and fetch each of them into a `Nextras\Dbal\Result\Row` instance. The `Row` instance is a simple value object with property access:

/--php
$users = $connection->query('SELECT * FROM [users]');
Expand All @@ -72,7 +72,7 @@ foreach ($users as $row) {
}
\--

`Result` object implements `SeekableIterator`. You can use `fetch()` method to fetch a row, `fetchField()` to fetch the first field form the first row, or `fetchAll()` to return array of rows' objects.
The `Result` object implements `SeekableIterator`, so you can interate over the result. Also, you can use `fetch()` method to fetch a row, `fetchField()` to fetch the first field form the first row, or `fetchAll()` to return array of rows' objects.

/--php
$maximum = $connection->query('SELECT MAX([age]) FROM [users]')->fetchField();
Expand All @@ -83,7 +83,7 @@ $maximum = $connection->query('SELECT MAX([age]) FROM [users]')->fetchField();
Transactions & savepoints
=========================

The Connection provides convenient API for working with transactions. You can easily `beginTransaction()`, `commitTransaction()` and `rollbackTransaction()`. Usually, you need to react to an exception by calling rollback method. For such use case there is a `transactional()` helper method that make its callback atomic.
The Connection interface provides a convenient API for working with transactions. You can easily `beginTransaction()`, `commitTransaction()` and `rollbackTransaction()`. Usually, you need to react to an exception by calling the rollback method. For such use case there is a `transactional()` helper method that makes its callback atomic.

/--php
$connection->transactional(function (Connection $connection) {
Expand All @@ -97,7 +97,7 @@ $connection->transactional(function (Connection $connection) {
});
\--

If you call `beginTransaction()` repeatedly (without commiting or rollbacking), connection will use savepoints for nested transaction simulation. It is possible to disable such behavior by setting `nestedTransactionsWithSavepoint` configuration option.
If you call `beginTransaction()` repeatedly (without committing or rollbacking), connection will use savepoints for nested transaction simulation. It is possible to disable such behavior by setting `nestedTransactionsWithSavepoint` configuration option to `false`.

You may create, release and rollback savepoints directly through appropriate methods.

Expand All @@ -111,7 +111,7 @@ if ($isOk) {
}
\--

Connection also supports setting a transaction isolation level. The default isolation level depends default setting of your database.
Connection also supports setting a transaction isolation level. The default isolation level depends on the default setting of your database.

/--php
$connection->setTransactionIsolationLevel(IConnection::TRANSACTION_SERIALIZABLE);
Expand Down
4 changes: 2 additions & 2 deletions doc/menu.texy
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
- [Connection | default]
- [Param Modifiers | param-modifiers]
- [Modifiers | param-modifiers]
- [Result | result]
- [Query Builder | query-builder]
- [DateTime TimeZones | datetime]
- [DateTime | datetime]

/---div .[tuts]
Tutorials:
Expand Down
8 changes: 4 additions & 4 deletions doc/param-modifiers.texy
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Parameter Modifiers
###################
Modifiers
#########

Dbal allows you to escape and build safe SQL query. It provides these powerful modifiers:
Dbal allows you to escape and build safe SQL query. It provides these powerful parameter modifiers:

|* `%s`, `%?s`, `%s[]` | string | not nullable, nullable, array of
|* `%i`, `%?i`, `%i[]` | integer | not nullable, nullable, array of
Expand All @@ -12,7 +12,7 @@ Dbal allows you to escape and build safe SQL query. It provides these powerful m
|* `%blob`, `%?blob`, `%blob[]` | binary string | not nullable, nullable, array of
|* `%any` | | any value
|* `%_like`, `%like_`, `%_like_`| string | like left, like right, like both sides
|* `%json`, `%?json`, `%json[]` | any | not nullabe, nullable, array of
|* `%json`, `%?json`, `%json[]` | any | not nullable, nullable, array of

All modifiers require an argument of the specific data type - eg. `%f` accepts only floats and integers.

Expand Down
Loading

0 comments on commit 3c2043c

Please sign in to comment.