dpgsql 1.1.1
dpgsql: ^1.1.1 copied to clipboard
High Performance PostgreSQL Driver for Dart
Changelog #
1.1.1 #
- fix: the session settings (
Search Path,Application Name,Statement Timeout,Lock Timeout,Idle In Transaction Session Timeout) ran with a fixed 100 ms timeout perSET. On a server whose replies take longer (in another region, or busy starting many backends)DpgsqlConnection.open()failed withTimeoutException after 0:00:00.100000, andDpgsqlDataSourceignored the timeout and handed out the connection with theSETstill running: the settings were missing and the caller's query read the pending reply ("Buffer interno menor que o esperado"). They now use the connectionTimeout(15 s by default); the data source skips only a setting the server rejects and discards the connection on any other error - fix: the pool's health check and step-by-step reset used the same 100 ms
timeout, and the health check counted a timeout as healthy, returning a
connection with the
SELECT 1still running to the pool; they use the 5 s reset timeout and a timeout discards the connection
1.1.0 #
DpgsqlCommand.simpleQuery: the command goes through the simple query protocol (libpq'sPQexec) even withUse Extended Query For Unparameterized Commands=true, so a script with several statements (create ...; insert ...; begin; ...; commit;) runs in one round trip; with the extended protocol PostgreSQL refuses it (42601). It takes no parameters and cannot be prepared or pipelined (StateError); onlyexecuteReaderandexecuteNonQueryuse it (the otherexecute*methods throw instead of ignoring it).DpgsqlConnection.executeReadertakessimpleQuery:too.
1.0.3 #
-
Performance,
DpgsqlDataSource: the pool settings are read once, when the data source is created. Every checkout and return read them again from the connection-string builder, which looks a keyword up through all its aliases (and through every parameter when it is not set): about 34 µs of CPU per checkout and return, now 1 µs; a pooledselect 1is as fast as on a single connection. -
Performance,
DpgsqlDataSourcewithoutNo Reset On Close: a reused connection is reset in one round trip (ROLLBACK, RESET ALL, CLOSE ALL, UNLISTEN * and the session settings in one simple-protocol query). It took up to seven (a health check, each statement, each session SET, another health check): about 322 µs per checkout on a local server, now 166 µs. The reset is also the health check; a connection that does not answer it within 5 s is discarded instead of being handed out (its late answer would have been read by the next command). -
Performance: a parameterized query auto-prepared in the same round trip as its first execution keeps its result columns, so the next executions get binary results for the columns with a binary decoder; they all asked for text. With the text
timestampdecoding below, a 100-row result with a timestamp column took 3.4 ms instead of 0.34 ms. -
Performance:
date,timestampandtimestamptztext values (DateStyle ISO) are read digit by digit;DateTime.parsetook about 30 µs per value. BC dates, infinity and years after 9999 still go throughDateTime.parse. -
One-dimensional arrays are lists of their element type (
List<int?>,List<String?>,List<DateTime?>...), soas List<int?>works. They were aCastList<dynamic, dynamic>. -
Decode Extended Types As Text=true: the types without a natural Dart type come back as the text the server prints for them, in the text and the binary formats:interval('1 day 02:03:04'),time/timetz,inet/cidr/macaddr, geometric types ('(1,2)','<(1,2),3>'),bit/varbit,tsvector, ranges ('[1,5)','[2024-01-01,2024-02-01)') and their arrays (lists of texts);moneyas the decimal amount ('12.34', from the binary value or the lc_monetary text). Off by default (DpgsqlInterval, DpgsqlMoney, DpgsqlPoint, DpgsqlRange...). -
Result columns without a binary decoder (enums,
tsvector,regclassand the other reg* types, arrays of user-defined types, ...) came back as their binary bytes, which could not be told apart frombytea: an enum'ok'was[111, 107]. Results are now requested per column: binary for the columns with a binary decoder, text for the others, which come back as the server's text ('ok',"'a' 'b'",'pg_class','{ok,sad}'). A statement parsed in the same round trip as its execution (its columns are not known yet) gets every column in text, as the simple protocol does; a prepared or auto-prepared one gets the per-column formats. The PgRow APIs (executePgRows,forEachPgRow) still read every column in binary.writeBindno longer rejects per-column result formats (it compared their number with the number of parameters). -
float4read in text gives the same double as in binary (1.100000023841858for1.1;double.parsegave1.1). -
Exact
numeric:Decode Numeric As String=truereturns the server's text ('150.50','12345678901234567890.123456789','NaN'; arrays as lists of strings) andUse Custom Decimal=trueaDpgsqlDecimal, in the text and binary formats and on every read path. By default numeric is still the nearestdouble, which loses digits beyond about 15 significant ones and the scale.DpgsqlDecimaland theuseCustomDecimalflag existed but no connection option reached them. ADpgsqlDbType.numericparameter now accepts aString, anumor aDpgsqlDecimal(each handler accepted only its own type), and aDpgsqlDecimalparameter is sent as numeric.DpgsqlTypesConfigis still not read by connections. -
Fixed results shifting after an error raised while a response was being read (a cancelled statement, or a failure after
RowDescription): theReadyForQuerythat ends the response was left unread, so the next command returned[]and every later command the previous command's result. All reader and fast paths (executeMaps,executeRows,executeScalar, prepared statements,prepare, COPY) now consume the response before throwing; errors also carrydetailandhinton every path. -
Mapparameters (and lists of maps) are sent as JSON. They were sent withtoString()({a: 1}), whichjson/jsonbreject. -
With
Infer String Parameters As Unknown(the default), lists of strings are sent as untyped array literals, like single strings, so the server infers the element type:uuid_col = any(?)no longer fails withoperator does not exist: uuid = text. -
A second command on a connection that is still running one (sending, reading, an open reader or a COPY) used to interleave with the first: both read each other's responses, returning wrong rows or crashing the read loop with "Buffer interno menor que o esperado". Concurrent callers now wait for their turn, in call order (
DpgsqlConnection.queueConcurrentCalls, defaulttrue); an open reader or COPY keeps the turn until it is closed. WithqueueConcurrentCalls = falsethe second command fails immediately with a clearStateError. Pipeline mode is unchanged. -
DpgsqlConnection.runInTransaction(action): commits, or rolls back whenactionthrows. The transaction holds the connection: commands and transactions from other callers wait until it ends, while commands fromaction(on the transaction or the connection, concurrent ones included, recognized through aZonevalue) run inside it. A transaction opened withbeginTransaction()has no such protection.DpgsqlTransaction.isCompletedtells whether it was committed or rolled back. -
executeMapsandexecuteRowsof a command without rows (DDL,SET,DO, an INSERT without RETURNING, ...) left the response'sReadyForQueryunread: the next command returned that empty result and every later one the previous command's result. -
Notifications are not drained from the socket while a caller waits for its turn: the drain could race the next command's read.
-
LISTENnotifications are delivered while the connection is idle. They were read only when the next command ran on the listening connection. -
Protocol strings (column names, error and notice fields, notification payloads, parameter values) are decoded as UTF-8. They were decoded as Latin-1, so
"situação"or a Portuguese error message came out garbled. -
@nameand?placeholders are no longer read inside comments (--, nested/* */), dollar-quoted strings ($$...$$,$tag$...$tag$) or the backslash escapes ofE'...'strings. An apostrophe in a comment opened a string and shifted every placeholder after it (syntax error). -
DpgsqlConnection.onConnectionLostemits when the server closes an open connection;close()after that no longer writesTerminateto the closed socket (it raised an uncaught error). -
Bytes already read from the socket are no longer overwritten: rows, queued messages and values are views of the read buffer, which was compacted in place (or written again from its start once fully read) when more data arrived. A reader's current row changed when the caller awaited between
read()andgetValue()(wrong values, "Invalid radix-10 number"), andbyteavalues already returned changed with later commands. -
A value that fails to decode, or a
forEachPgRowcallback that throws, no longer shifts the later results: the rest of the response is read first. -
Prepared
executeMaps(the fast path) reads up toReadyForQuerybefore throwing a server error: it threw at once, and the next command returned the previous one's leftovers. -
A batch (
createBatch,executeCommandsPipelined) whose first command fails reads itsReadyForQuery: the next command returnednulland every later one the previous command's result. -
A parameter that fails to encode (a
Mapholding a non-JSON value, an out-of-rangeint) no longer hangs the connection:Parsewas buffered before the values were encoded and sent ahead of the next command, which the server then skipped. -
intparameters beyond the int4 range are sent as int8; they were truncated to 32 bits (2^31 arrived as -2^31).DpgsqlDbType.bigintandsmallintencode 8 and 2 bytes (they sent int4), and out-of-range values for int2/int4 throw anArgumentErrorinstead of being truncated. Lists with values beyond int4 are sent asint8[]. -
Binary
numericNaN,Infinityand-Infinitydecode as such (they were 0.0), and binary values decode to the same double as their text (the digits were accumulated with a rounding at each step). There is still no exactnumerictype: usePgResultMode.rawTextor cast to text. -
timeandtimetzin the binary format decode as PostgreSQL prints them (12:34:56.789,12:34:56+05:30), like the text format; they came back as raw bytes.oiddecodes as anintandxmlas aString. -
Arrays of
int2,int8,numeric,oid,varchar,bpchar,"char",bytea,date,time,timetz,timestamp,timestamptz,interval,uuid,json,jsonb,inetandcidrare decoded; they came back as raw bytes (binary) or as the literal text ({1,2}). AList<Uint8List>parameter is sent asbytea[](it was read as nested int arrays). -
Text-format dates and timestamps before Christ (
0044-03-15 BC) decode; they threw aFormatException. -
moneyin the text format honors the server locale's separators:R$ 1.234,56came back as 1.23 andR$ 12,34as 1234.00. -
An interval whose time part is negative with zero hours (
-1 days -00:00:01) keeps its sign in the text format. -
A JSON
null('null'::jsonb) decodes asnullinexecuteMaps/executeRows; it came back as raw bytes. -
executePgRows,forEachPgRowandforEachPgRowSyncwithout a prepared statement return real binary rows with the column names and types: they were rebuilt from text values namedcolumn_0, column_1, ..., sogetInt,getDouble,getDateTimethrew andgetByNamefailed. They now use the extended protocol (one statement per command). -
executeMaps,executeRowsandexecuteScalarof several statements return the rows of a statement that follows one without rows (create ...; insert ...; select ...); they were dropped. -
executeScalarreads the whole response, so an error raised after the first row or by a later statement is thrown (the first value of a failed statement was returned), and statements without rows before the query are skipped, as inexecuteMaps. -
executeNonQueryreads every statement and returns the sum of their counts: only the first was read, so a failing later statement went unnoticed. -
$1, $2, ...placeholders bind the parameters in collection order; the parameters were dropped (08P01 / 42P02). Parameter names may carry the Npgsql@or:prefix (addWithValue('@id', 1)). -
An auto-prepared statement the server rejects after a schema change ("cached plan must not change result type", 0A000) or no longer has (26000,
DEALLOCATE ALL) fails once and is prepared again; it failed on every later execution. An unparameterized command without rows (Use Extended Query For Unparameterized Commands) no longer fails its third run with "prepared statement already exists" (42P05). -
A failed COPY (a rejected row, an error in the middle of
COPY TO) ends the operation and leaves the connection usable; it kept the connection's turn and every later command waited forever. ACOPY TOclosed before its end, a cancelledCOPY FROM(cancel(),DpgsqlBinaryImporter.close()) and a failed binary export read the rest of their response instead of leaving it to the next command. -
cancel()closes the socket of its cancel request; it stayed open and kept the process from exiting. -
Notices, notifications and
onConnectionLostare delivered onDpgsqlDataSourceconnections; they were wired only byopen(), soLISTENdid not work on pooled connections. -
A command on a connection the server closed fails at once with a
StateError; it wrote to the closed socket (aSocketException, or an uncaught assertion ofdart:ioin debug mode).
1.0.2 #
- Fixed decoding of the system-catalog string types
name(OID 19) and the internal single-byte"char"(OID 18): they were returned as raw bytes (Uint8List) instead ofString. For exampleSELECT datname FROM pg_databasenow returnsStringvalues. Also registered a textArrayHandlerforname[](OID 1003). Application string types (text,varchar,char(n)) were already handled correctly.
1.0.1 #
- Added
Use Extended Query For Unparameterized Commands, allowing ORM/query-builder workloads to use extended protocol and binary result decoding for repeated single-statement queries without parameters. - Added auto-prepare support for unparameterized
executeMaps()when extended query mode is enabled, reusing cachedRowDescriptionmetadata. - Optimized prepared
executeMaps()with cached map metadata and a specialized raw message reader for the hot path. - Added buffered backend-message draining via
PostgresMessageReader.tryReadMessage(), reducing awaits when the socket already delivered multiple PostgreSQL messages. - Reduced fixed query overhead in pooled ORM workloads with a no-reset pool fast path and synchronous safe return of pooled connections.
- Reduced per-execution overhead for unparameterized
DpgsqlCommandcalls by bypassing execution-plan allocation when no rewrite is needed. - Removed temporary
ByteData.sublistViewallocations from primitive binary input/output paths. - Improved Eloquent/SALI performance: larger AOT benchmark samples now show
dpgsqlmatching or beatingpostgresql-forkon measured real SALI queries under pool concurrency, while still trailing on theSELECT 1microcase. - Re-ran driver comparison benchmarks against
postgres_fork,postgres, PHPext-pgsql, PHPPDO_PGSQL,voryx/PgAsync, andamphp/postgres;dpgsql_aotremains ahead of Dart drivers on most result-set scenarios and wins application typed class + JSON serialization against measured PHP drivers. - Added focused tests covering the extended unparameterized query option and auto-prepared unparameterized
executeMaps().
1.0.0 #
- Added PostgreSQL real integration coverage for pipeline, COPY, pooling, encodings, notifications, error recovery, and common type decoding.
- Added GitHub Actions workflow with PostgreSQL 14, 15, 16, and 17.
- Added README documentation with installation, pooling, prepared statements, batch/pipeline, COPY, notifications, encodings, tests, and benchmarks.
- Added benchmark comparison against
postgres_fork,postgres, PHPext-pgsql, PHPPDO_PGSQL,voryx/PgAsync, andamphp/postgres. - Added application-level typed class + JSON benchmark to force PHP and Dart type conversion, object hydration, and serialization work.
- Added bundled codecs for PostgreSQL client encoding support.
- Implemented robust connection pooling with checkout wait queue, warmup, idle lifetime, connection lifetime, and pool metrics.
- Implemented lazy row decoding and fast paths for common scalar result types.
- Added typed data reader getters and optimized DataRow parsing/message reads for result-set hot paths.
- Added materialized
executeRows()and lazyexecutePgRows()result-set APIs. - Added
executeMaps(),DpgsqlDataReader.toMap(), andDpgsqlDataReader.readAllMaps()for ORM-style map rows. - Added
PgResultMode.rawTextfor PHP-styleString/nullresult access and raw text map benchmarks. - Added
DpgsqlCommand.forEachPgRow()for streaming transient lazy row views without result-list materialization. - Added configurable
TimeZoneSettingsfor UTC/default and local timestamp/date decoding, including pooled session restore forTimeZoneandclient_encoding. - Added opt-in vendored PostgreSQL/IANA timezone support for named
timestamptzdecoding without adding external runtime dependencies, withlatest_allas the robust default andIANA Time Zone Database Scope=latest_10yas the compact runtime option. - Added nullable PostgreSQL date/time infinity handling by default, with
Throw On DateTime Infinity=truefor strict behavior. - Added a pure Dart IANA timezone generator that parses
Rule,Zone, andLinkrecords withoutzic.c,package:timezone, or external reference directories; bothlatest_alland compactlatest_10ygenerated databases are supported, and the runtime-facinglatest.tzfdefault filename was removed. - Moved Dart package-driver benchmark dependencies to
benchmarks/pubspec.yamlso the main package stays free of benchmark transitive dependencies. - Added
result_sets_mapsbenchmark coverage forMap<String, dynamic>row materialization. - Added PHP associative-map benchmark coverage and 3000-row result-set scenarios.
- Added
DpgsqlCommand.forEachPgRowSync()and cached command execution plans to reduce per-row and per-execution overhead in hot paths. - Optimized repeated unprepared command planning by replacing per-execution string signatures with structural cache checks.
- Added default PostgreSQL type inference for untyped string parameters (
Infer String Parameters As Unknown=true), improving ORM compatibility for ISO timestamp strings and other context-typed columns while preserving explicitDpgsqlDbType.text/varcharbehavior. - Documented and validated Laravel/Eloquent-style
DateTimebindings: ORMs may formatDateTimeasyyyy-MM-dd HH:mm:ss, anddpgsqlkeeps those untyped strings as PostgreSQL-inferred parameters by default sotimestamp without time zonecolumns preserve wall-clock time. - Added
Decode Uuid As String=truedefault for ORM/PDO compatibility, withDecode Uuid As String=falsepreserving Npgsql-likeDpgsqlUuiddecoding. - Added parsed
json/jsonbdecoding by default for ORM/PDO compatibility with nested model maps/lists, withDecode Json As String=truepreserving raw JSON string decoding. - Added non-string configuration entry points via
DpgsqlConnection.fromConnectionStringBuilder()andDpgsqlDataSource.fromConnectionStringBuilder(), allowing integrations to pass configured settings directly instead of serializing a connection string. - Applied configured session settings (
Search Path,Application Name,Statement Timeout,Lock Timeout, andIdle In Transaction Session Timeout) directly fromDpgsqlConnectionStringBuilderfor both standalone connections and pooled data sources. - Optimized binary date/timestamp/timestamptz conversion by avoiding
Durationallocation in hot paths. - Implemented text parsing for PostgreSQL
byteaandnumeric. - Implemented text parsing for PostgreSQL range types.
- Fixed protocol recovery after
ErrorResponseduring reader initialization. - Hardened auto-prepare fallback when no safe prepared statement slot is available.
- Implemented simple and extended PostgreSQL query protocol support.
- Added parameters, prepared statements, auto-prepare cache, batch execution, and pipeline mode.
- Added binary COPY import/export support.
- Added SSL modes, SCRAM-SHA-256 authentication, cancellation, and notifications.
- Added handlers for scalar types, arrays, JSON/JSONB, geometric types, ranges, timestamps, intervals, numeric, money, and bytea.
- Added large object and logical replication scaffolding.