eloquent 4.3.0
eloquent: ^4.3.0 copied to clipboard
Laravel-style (Eloquent 5.2) query builder, schema builder and migrations for Dart, with PostgreSQL and MySQL drivers.
4.3.0 #
Security: an operator or a JSON selector that came from outside could carry SQL.
where,orWhere,whereNot,whereAny,whereAll,having,join,joinWhere,whereDateand the other date clauses,whereJsonLengthand the subquery wheres put the operator in the SQL as it came:where('id', '= 0 or 1=1 or 1 =', 1)returned every row. They now accept only known operators and throwArgumentErrorfor any other. If your code builds a query from an operator it received, as a search filter does, update.where('data->x', ...)with a quote in the key on PostgreSQL and on MySQL, and a backslash in the path ofwhereJsonContainson MySQL and MariaDB, ended the string and could carry SQL. They are escaped now, and in the JSON key of anupdateon MySQL too.whereColumn('a', 'unknown', 'b')was silentlya = unknown, droppingb. It throws now.whereColumn('a', 'b')is stilla = b.
Operators:
custom_operatorsin the connection config, andConnection.addCustomOperators, add the operators of an extension or of aCREATE OPERATOR, on any database:['<=>', 'OPERATOR(geo.<->)']. They also apply towhereSqlOperator.- On PostgreSQL every operator of
pg_operatoris accepted, and a test compares the list with the server's catalog.whereSqlOperatoraccepts all the ones that return a boolean. ?,?|,?&and@?work on PostgreSQL, compiled tojsonb_exists,jsonb_exists_any,jsonb_exists_allandjsonb_path_exists_opr, as?is the bind placeholder. They failed with a wrong number of parameters.SqlOperator.postgresRegexAllandpostgresNotRegexAllwere~///and!~///. They are~*and!~*, sowhereFlex('a', '~*', 'x')works.- On MySQL
#is no longer an operator: it starts a comment.
4.2.1 #
postgres_v3: a dateDateTimecannot represent (infinity,-infinity, a year past its range) is nownull, as on the other adapters, with anypg_types, and in arrays.'infinity'::datecame back as the wrong date36070-01-11, and'infinity'::timestampfailed with aRangeErrorno caller could catch, which ended the process. Dates before Christ keep their value.postgres_v3: postgres 3.5.7 has errors no caller can catch, which by default end the process: a write to a socket the server reset, a script with two statements that return rows, a malformed message. The adapter now opens its sockets in a zone that drops them. The call they belong to still fails, or succeeds, by itself, and your own errors reach you as before.- Requires dargres 4.1.1, dpgsql 1.3.1 and postgres_fork 3.1.2: an error writing to a socket the server reset no longer ends the process there either.
4.2.0 #
- The
dargresanddpgsqladapters no longer send the password in clear text when the server asks for it with no verified TLS: whoever asks gets the password itself, so withsslmoderequire, or without TLS, that could be anyone between the application and the server. The connection fails instead. With a server that authenticates with LDAP, PAM orpassword, usesslmodeverify-full, ordriver_optionsallowCleartextPassword: true(dargres) or'Allow Cleartext Password': 'true'(dpgsql).postgresalready refused it, and itsallowClearTextPasswordnow also works withpool. Onpostgres_v3the postgres package has no such option: it still sends it. - The dpgsql adapter requires dpgsql 1.3.0: opening a connection is now bounded
by
Timeout(15 s by default; it had no limit, so a server that stopped answering left it waiting forever), and bytes it cannot read on an idle connection lose that connection instead of ending the process. - The dargres adapter requires dargres 4.1.0, and the postgres adapter postgres_fork 3.1.1.
- README: what each
sslmodedoes, the host name a certificate must name, whatrequiredoes not protect from, and a "What's new in 4.1" section.
4.1.0 #
- TLS certificates on every adapter: the
sslrootcert,sslcert,sslkey(the names Laravel uses for PostgreSQL) andsslpasswordconfig keys, andsecurity_contextfor aSecurityContextwith certificates in memory. A private CA can verify the server withsslmodeverify-caorverify-full(only the system roots could), and the connection can present a client certificate. Settings that cannot be honored throwArgumentErrorinstead of connecting with less security:sslrootcertin a mode that does not verify, any certificate withsslmodedisableor absent, a certificate without its key, a file that cannot be loaded. - The mysql adapter requires mysql_dart 4.0.1.
sslmodekeeps its meaning:require,allowandpreferuse TLS without verifying the certificate,verify-caandverify-fullverify it. - The dpgsql adapter requires dpgsql 1.2.0, and the postgres adapter postgres_fork 3.1.0.
4.0.0 #
Details and migration notes: doc/release_notes_4.0.0.md.
Breaking changes #
ConnectionInterface:transaction<T>()is generic, and implementations must addtransactionIfNeeded<T>(),isInTransactionandsupportsSavepoints.transaction()inside a transaction now creates a SAVEPOINT instead of a new transaction, andtimeoutInSecondsis passed to the driver.InvalidArgumentExceptionreplaced byArgumentError.- Time columns (
timestamp,dateTime,timeand theTzforms,timestamps(),softDeletes()) have no precision by default (timestamp without time zone, microseconds on PostgreSQL); they weretimestamp(0), which rounds the fractions that 4.0 writes to the next second.SchemaBuilder.defaultTimePrecision = 0restores Laravel's default;precision:sets one column. Blueprint.toSqlthrows for a command that does not compile or is unknown (it printed a warning and skipped the DDL).Manager.bootEloquent()(empty) andManager.setFetchMode()(wrote adatabase.fetchsetting nothing read) removed.Containerstubs that returned null (getMethodDependencies,getCallReflector,getDependencies,resolveNonClass,resolveClass,keyParametersByArgument,getFunctionHint) and the staticContainer.objectremoved.- The dargres adapter requires dargres 4.0.1.
- The dpgsql adapter requires dpgsql 1.1.1 (
DpgsqlCommand.simpleQuery; the session settings no longer time out after 100 ms, which failed connections and, on a pool, handed them out without the settings under load). - The mysql adapter requires mysql_dart 3.0.3. A DATE/DATETIME that
DateTimecannot represent (the zero date0000-00-00, a zero month or day,2024-02-30under ALLOW_INVALID_DATES) reads asnull; it came back as another day (0000-00-00as-0001-11-30).driver_optionszeroDateBehavior: 'exception'throws instead. where(col, op, null)with an operator other than=,<>or!=throws (it silently becameis not null);where(col, value)means= value.value(col)returns the column value instead of the row;update()andConnectionInterface.updatereturnFuture<int>.count()on grouped/distinct/union queries counts their rows (it returned the first group's count);count('*', CountMode.laravel)keeps Laravel's semantics.- PostgreSQL
update/deletewith a left join andrecursionLimit()on PostgreSQL/MySQL throw instead of producing wrong or ignored SQL. - PostgreSQL values are the same Dart types on the four adapters
(
pg_types: 'uniform', the default): the types without a natural Dart type (time,timetz,interval,inet,cidr,macaddr, geometric types,bit,tsvector, ranges) are the server's text (postgres_fork returnedPgTime,PgInterval,PgInet,PgPoint; postgres_v3Time,Interval,IntRange...; dpgsqlDpgsqlInterval,DpgsqlRange...),moneythe decimal amount, arraysList<T?>, arrays of user-defined types (enum[]) the array literal.pg_types: 'driver'keeps each driver's types. See the release notes. - A unique violation (PostgreSQL
23505, MySQL 1062/1586) throwsUniqueConstraintViolationException(aQueryExceptionwith the driver's exception inpreviousand its text intoString()) instead of the driver's exception; other errors are not wrapped. - A statement that loses its connection while it runs is not run again:
the server may have committed a write (running it twice duplicated the
row), and a select can have side effects too (
nextval, a function that writes). The connection is reopened for the next statement and the error is thrown. A statement the driver refused before sending it (a connection the server had already closed, issue #2) is still retried. Concurrent reconnections of a connection share one (each made its own and left the others' PDOs open). - With
'pool': true, a lost connection no longer closes and remakes the whole pool, which failed the statements other requests were running on it: the pool discards the dead connection (pool_reconnect: 'keep', the default).pool_reconnect: 'replace'keeps the old behavior. A TLS handshake or certificate failure is not taken for a lost connection (it was retried 10 times, about 40 s). sslmodeis honored by every adapter (disable,allow,prefer,require,verify-ca,verify-full, and the MySQL names): postgres and dargres ignored it and connected in plain text, postgres_v3 only knewrequire, dpgsql trusted any certificate inverify-full. An unknown value, or a mode the adapter cannot honor (verify-full on a postgres pool), throwsArgumentErrorinstead of connecting with less security. The dargres adapter requires dargres 4.0.2 (its TLS handshake always failed).- An unknown
driver_implementationthrowsArgumentError(it fell back topostgres);portis optional (5432, 3306 for mysql);pool,poolsizeandportaccept strings. min/max(QueryBuilderandQueryBuilder2) are generic: without a type they return the driver's value (aDateTimefor a date column, text for a PostgreSQLnumeric);final num? m = await q.max('x')still gets a number. They threw on date columns.pretend()covers only the code of its callback (a Zone): another request on the same connection was pretended too, and a query started in the callback but run after it is not pretended.pretendingPropis deprecated.- A query listener that throws no longer turns a successful statement into
an error: its error goes to
listen(callback, onError: ...), or is printed. outsideTransaction()inside atransactionScopeneedspoolsize >= 2(it waited forever on a pool of one).Connection.tryReconnectCountandreconnectIfMissingConnection()were removed;getElapsedTimereturns milliseconds (it returned nanoseconds).DatabaseManagerkeepsname::read/name::writeapart fromname, and concurrentconnection(name)calls share one connection (the second one leaked).- MySQL:
strict: falseturns strict mode off (it turned it on), the strict modes no longer includeNO_AUTO_CREATE_USER(removed in MySQL 8),charset/collationwork as in Laravel (charset: 'utf8mb4'failed with "Unknown collation"). QueryBuilder:setBindingstakes a List;whereBetweenneeds two bounds;insertManyreturnsPDOResults, uses the keys of every row and inlinesQueryExpressionvalues;inRandomOrdertakes an integer seed only.Utils/Arr/Fluentfollow their PHP namesakes:explodewith a limit,arrayGetwith dot notation,arrayPop,arrayShift,arrayRand,mapExceptSd,ltrim,substr,empty,strval,endsWith,Fluent.isset,Str.parseCallback.Utilsmembers renamed to lowerCamelCase (no aliases):array_map→arrayMap,array_filter→arrayFilter,in_array→inArray,str_replace→strReplace,array_pluck→arrayPluck,map_change_key_case_sd→mapChangeKeyCaseSd,array_get→arrayGet,data_get→dataGet,array_accessible→arrayAccessible,is_object→isObject,array_collapse→arrayCollapse,array_shift→arrayShift,array_exists→arrayExists,string_contains→stringContains,array_merge→arrayMerge,array_merge_ss→arrayMergeSs,array_merge_ms→arrayMergeMs,array_merge_sa→arrayMergeSa,array_flatten→arrayFlatten,map_merge_sd→mapMergeSd,map_add_sd→mapAddSd,map_except_sd→mapExceptSd,map_unset_deep_sd→mapUnsetDeepSd,array_unshift→arrayUnshift,array_rand→arrayRand,is_array→isArray,is_map→isMap,is_bool→isBool,is_string→isString,array_is_empty→arrayIsEmpty,is_null→isNull,is_null_or_empty→isNullOrEmpty,map_key_exists→mapKeyExists,map_first_key→mapFirstKey,array_end→arrayEnd,var_dump→varDump,str_length→strLength,array_pop→arrayPop,map_values→mapValues,int_max→intMax,array_fill→arrayFill.Utils.explodereturnsList<String>.- Stricter types:
QueryBuilder.bindingsisMap<String, List<dynamic>>;prepareBindingsreturns aListandpretendaFuture<List<Map<String, dynamic>>>;Connection.setPdo/setReadPdoreturnConnection,getPostProcessoraProcessor?andgetDefaultSchemaGrammaraSchemaGrammar?;DatabaseManager.connectionsPropholdsConnections; some untypedQueryBuilderparameters (boolean,not,type,operator,column) are typed.
Added #
pg_types('uniform'|'driver') anddriver_options(settings passed to the driver; an unknown key throws with the list of known ones) config keys;PDOConfig.fromMapreads snake_case and camelCase spellings (application_name/applicationName,poolsize/poolSize,allowreconnect/allowReconnect,is_unix_socket/isUnixSocket), soPDOConfig.fromMap(config.toMap())round-trips.- MySQL
driver_optionszeroDateBehavior('convertToNull', the default, or'exception'; also theZeroDateBehaviorvalue) for datesDateTimecannot represent. test/pg_type_parity_integration_test.dart: ~55 PostgreSQL types read cold, warm, in one row and inside a transaction must give the same Dart type and value on the four adapters.QueryBuilder2(db.table2()): Dart 3 API with fixed-meaning arguments (where(column, value, {operator})),CompOperator/SortDirectionenums, typed group/join builders,whereSqlOperatorvalidated per dialect and typed results,paginate/simplePaginate(PageResult),OnConflict,insertReturning/updateReturning/deleteReturning,upsert,insertOrIgnore,unionand (recursive) CTEs; compiled by the existing grammars.datetime_bindings: 'native'sends DateTime bindings to the driver unchanged (microseconds kept, correct instant fortimestamptz); the default'string'keeps Laravel'syyyy-MM-dd HH:mm:ssformatting.- PostgreSQL,
datetime_bindings: 'string'(default): a DateTime with fractional seconds keeps them (2024-03-10 12:34:56.789123; 10:20:30.123 was stored as 10:20:30). Whole seconds are formatted as before. MySQL keeps whole seconds (itsDATETIMEwould round a fraction instead of truncating it). NewGrammar.formatDateTime, also used bytoRawSql(). - PostgreSQL LISTEN/NOTIFY on every PostgreSQL adapter:
db.notify()(transactional, viapg_notify),db.notifications(channel)andPostgresNotificationListener(one dedicated connection, reconnection, heartbeat,ready(),onReconnect). - Nested transactions via SAVEPOINT,
transactionIfNeeded(),isInTransactionand a realtransactionLevel()(issue #6). - Row locks:
lockForUpdate({skipLocked, noWait, of}),sharedLock(...),skipLocked(),noWait(). groupByRaw(sql, bindings);groupByacceptsdb.raw(...)(it was silently ignored) and rejects other values.upsert(),insertOrIgnore(),onConflict()/doUpdate()/doNothing()andreturning().- PostgreSQL JSON, full-text,
distinctOn()and array operators (whereArrayContains,whereArrayContainedBy,whereArrayOverlaps). cursor()/lazy()streaming anddriver()access (COPY, LISTEN/NOTIFY) for dpgsql.- Migrations, seeders and CLI (
make:migration,make:seeder,schema:diff); transactional migrations. - Working schema introspection and diff →
ALTER TABLEfor PostgreSQL. - Doctrine schema port completed:
Schema.toSql()/toDropSql(),PostgreSQLPlatform,MySQLPlatform,MariaDBPlatform(db.getDatabasePlatform()),Comparator.compareSchemas()+getAlterSchemaSQL(),introspectSchema(), namespaces, MATCH/DEFERRABLE foreign keys, UNLOGGED tables. - ORM without code generation or reflection, in two styles: Active Record
(
Model<T>with plain fields,toMap()/fill()andmodelDefinition; orAttributeModel<T>with the columns in a map and typed getters/setters;ModelDefinitionwithfillable(assign()),hidden(toJson()) andinsertNulls,ModelQuery, casts,save()of the changed columns,HasOne/HasMany/BelongsTo/BelongsToMany,latestOfMany/ofMany, eager loading in batches, global and local scopes, soft deletes, observers) and Data Mapper (Repository<T>,EntityMapper<T>). DargresPDOappliessearch_path, encoding, time zone, application name and timeouts like the postgres_fork adapter;numericas exactString.- pubspec
repository,issue_tracker,documentationandtopics. - From Laravel 12:
db.listen(),onSlowQuery(),totalQueryDuration()and theQueryExecutedevent;toRawSql()/dumpRawSql();when(),unless(),tap();whereAny/whereAll/whereNone/whereNot;whereLike(case-insensitive by default);orderByDesc,reorderandorderBywith a subquery;select/addSelectwith subqueries,fromSub,leftJoinSub/rightJoinSub/crossJoinSub;lazyById,eachById,chunkByIdDesc,chunkMap;sole,soleValue,firstOrFail,findOr,doesntExist,existsOr;havingBetween,havingNull, nestedhaving;whereIntegerInRaw(no 65535 parameter limit);incrementEach/decrementEach; MySQLwhereFullText. db.transactionScope((ctx) ...): an ambient transaction; inside it, code that usesdb(a repository called from the callback) runs in the same transaction instead of another pooled connection (or waiting forever on a single connection),db.transactionbecomes a SAVEPOINT anddb.isInTransactionis true.db.outsideTransaction(...)leaves the scope on purpose (needs a pool);isInTransactionScope.- Search and filters:
whereSearch(columns, term, {match, unaccent, everyWord})(the term matched literally:%/_escaped; a null or blank term adds nothing),whereAnyLike,unaccent: trueonwhereLike(PostgreSQLunaccent(col::text) ilike unaccent(?),QueryPostgresGrammar.unaccentFunction),lower: true(lower(col) like lower(?), for expression indexes and case-sensitive MySQL collations),QueryBuilder.escapeLikeandlikePattern;orderByInput(field, direction, allowed: {...}, fallback:)orders by a client parameter through an allow-list. whereInTuple/whereNotInTuple((a, b) in ((?, ?), ...)),whereRowValues((a, b) > (?, ?)),whereAnyArray/whereNotAnyArray(col = any(?)with one array parameter,in (...)on MySQL),whereBetweenColumns,whereValueBetween.- Migrations:
Migrator.runPending()(the registered migrations not run yet, from the registry alone, so it works in an AOT build) andpending();markAsRan()(baseline of an existing database);Migrator.isolated(default on PostgreSQL: each migration takes an advisory lock and checks the log again, so concurrent deploys apply it once); the log is written in the migration's transaction (transactionScope), so a failure rolls both back;Migration.connectionandMigration.statement(sql)for raw SQL; the migration table gets anidprimary key. - PostgreSQL column comments (
comment(...)) becomeCOMMENT ON COLUMN(they were ignored); MySQL time columns takeprecision:(datetime(3)). cursorPaginate()(keyset pagination with opaque cursors,CursorPage),paginateWithWindowCount()(opt-in one-query pagination withcount(*) over ()),explain({analyze}),afterQuery(callback),updateFrom().- Bulk:
copyInto(rows)(COPY on dpgsql, multi-row INSERTs elsewhere),insertUnnest(rows, types:)(insert ... select * from unnest(?::int[], ...), PostgreSQL);nextValue(column)(max + 1 per scope underpg_advisory_xact_lock). cursor()on every PostgreSQL adapter: the adapters without an incremental reader use an SQL cursor (DECLARE ... FETCHin a transaction);PDOExecutionContext.supportsQueryStream.QueryBuilder2:whereAny/whereAll/whereNone,whereDate/Time/Day/Month/Year, JSON (whereJsonContains,whereJsonLength), arrays (whereArray),whereFullText,distinctOn,joinSub,joinLateral/leftJoinLateral,latest/oldest/inRandomOrder,db.unprepared(sql)anddb.execute(sql)(Laravel'sDB::unprepared, PHP'sPDO::exec) use the simple query protocol on the four PostgreSQL adapters, so a script with several statements, dollar-quoted bodies and its ownBEGIN/COMMITruns the same everywhere; postgres_v3 and dpgsql sent it through the extended protocol, which refuses more than one statement (42601). Queries with bindings keep the extended protocol.UniqueConstraintViolationException(Laravel 10+) with the violatedconstraint, on the five adapters, also when it ends a transaction on postgres/postgres_v3.createOrFirst()/firstOrCreate()(insert or return the row a unique index already has;on conflict do nothing returning *on PostgreSQL, so a surrounding transaction stays usable).- PostgreSQL locks:
db.advisoryLock(key)/db.tryAdvisoryLock(key)(pg_advisory_xact_lock, held until the transaction ends) andlockTable(TableLockMode, {noWait})(LOCK TABLE, mode from an enum).cursor,chunk,chunkById,insertUsing,firstOrFail,createOrFirst,firstOrCreate,lockTable, and every item above.
Fixed #
-
A PostgreSQL transaction whose callback caught an SQL error (without a savepoint) reported success on dargres and dpgsql, although the server answered its COMMIT with a ROLLBACK and nothing was saved. It now fails with an error on every PostgreSQL adapter. Requires dargres 4.0.3 and dpgsql 1.1.3.
-
postgres_v3: an SQL error inside a nested transaction (savepoint) rolled back the whole transaction, because postgres 3.xrunTxfails the transaction after any error. The adapter sends BEGIN/COMMIT itself on a reserved connection (from the pool, or the single connection under its lock) and tracks whether the server aborted the transaction, soROLLBACK TO SAVEPOINTrecovers as on the other adapters. A COMMIT or ROLLBACK that fails without an answer from the server closes that connection, so it is never reused with the transaction in an unknown state. -
A connection the server closed for being idle was not always seen as lost: PostgreSQL 14+
idle_session_timeout(SQLSTATE 57P05, "terminating connection due to idle-session timeout") made every later query fail on dargres; it reconnects, as MySQL/MariaDBwait_timeoutalready did (issue #2). Tested on every adapter, with and without a pool. -
postgres (postgres_fork): an SQL error inside a nested
transaction()(SAVEPOINT) rolled back the whole transaction, as the driver did not know a savepoint was active; nested transactions use the driver's ownsavepoint()(also through the pool), so the error rolls back to the savepoint and the outer transaction goes on, as on dargres, dpgsql and MySQL. NewPDOExecutionContext.runInSavepointfor such drivers. -
unprepared()inpretend()threw aTypeError(it returnedtrueas the affected rows); it returns 0. -
MySQL
increments()in a create or add failed with error 1075 (the auto_increment column was not declared a key); it isprimary keyinline, as in Laravel. -
Manager.connection(),table()andschema()used the global instance (instance!), so they failed with a null check withoutsetAsGlobal(); they use the manager they are called on. -
IoC
Container: factories were called with the wrong arguments (every closure binding failed); they are called with(),(container)or(container, parameters).extend,rebinding, contextual bindings (when().needs().give(), which also passed a method instead of the abstract) and[]=with a value crashed;sharecached one value for all closures;makeof an unknown type crashed withNoSuchMethodError(BindingResolutionExceptionnow); a binding to another name threwUnimplementedError;resolving/afterResolvingdid nothing; empty list/map instances were notbound;call/wrappassednullinstead of the parameters;refreshandClass@methodthrowUnsupportedError(they need reflection). -
dpgsql:
EXPLAIN,VALUES,TABLE,FETCHand parenthesizedSELECTs returned no rows (they were run as statements without a result). -
postgres_v3:
inet,cidr,macaddrandoidcame back as garbage strings (binary bytes decoded as text) andmoney,timetz,bit,regclass,enum[]and arrays ofnumeric,moneyandintervalthrewFormatException. -
postgres_fork:
driver_optionsqueryTimeoutInSeconds(or the pool'squeryTimeout) is the default per-query timeout; each query passed its own 30 s, so the connection's default never applied. Same for postgres_v3queryTimeoutand dargrescommandTimeout(pool). -
dpgsql: a statement on a connection the server closed is detected as a lost connection and retried on a new one (dpgsql 1.0.3 reports it as "the connection to the server was lost").
-
Query builder:
groupBy(raw)ignored,whereIn([]),exists()read connection,union(Function),updateOrInsert,clone(),paginateside effects, builder left without orders/limit after a failed count. -
PostgreSQL:
deletewith joins (USING),update/deletewithlimit(ctid), CTEs inupdate/delete, update binding order. -
MySQL: CTEs in
select/update/delete; session settings (set names,time_zone,sql_mode) on every pool connection. -
Timeouts: forwarded by
get/insert/update/delete/selectOne; session timeouts on postgres_v3 and MySQL; snake_case timeout keys; per-call timeouts on dargres without pool; dpgsql cancels timed-out commands and replaces the connection. -
dpgsql: affected rows of
WITH ... UPDATE/DELETE. -
MySQL:
exists()was always false (1 == true), soupdateOrInsertalways inserted. -
whereColumn([...])bound the second column as a value. -
MySQL
insertGetIdcould return another request's id (a separateSELECT LAST_INSERT_ID()ran on another pooled connection, or after another caller's insert); the id now comes with the INSERT result (PDOResults.insertId). -
postgres_v3 and dargres without pool: a request that arrived during another one's transaction failed; callers of the single connection are serialized, like dpgsql and MySQL.
-
dpgsql:
numericcomes back as the exact server text ('150.50'), as on postgres, postgres_v3 and dargres; it was adouble(150.5, digits lost beyond ~15). Uses dpgsql 1.0.3'sDecode Numeric As String. -
dpgsql single connection: transactions run through
DpgsqlConnection.runInTransaction(dpgsql 1.0.3), which keeps other callers' commands out of them even without the adapter's lock. -
union+orderByRawwith bindings emitted them before the union parts' bindings; pagination counts of a union kept its limit. -
MySQL
onConflict()emitted PostgreSQL'son conflict; it is compiled ason duplicate key update. -
Lost connections: dpgsql and MySQL without pool were not detected (every later query failed); detection now uses the error codes and the messages of each adapter, without matching data such as a CEP.
-
Migrations:
foreign()produced no SQL (PostgreSQL and MySQL),schema.create()ran an empty query on MySQL, anddropColumn(),dropForeign()andrename()produced no SQL on PostgreSQL;deferrable()/initiallyDeferred()are compiled on PostgreSQL. -
Doctrine
Table.addForeignKey()always threw;renameColumn()updates indexes, unique constraints and foreign keys. -
where/havingwith aListvalue (Uint8List, PostgreSQL array, JSON) spread it into several bindings for one placeholder. -
pretend()returned before an async callback ran (empty log) and ranselects for real; it now awaits the callback and restores the log. -
chunkByIdreused the same builder between pages and did not detect a missing id column. -
PostgreSQL
whereFullTextvalidates the language (it is inlined in SQL). -
dpgsql and MySQL without pool serialize concurrent callers of the single connection, like the other adapters (they failed with "connection is busy").
-
postgres_fork: same duplicate-column mapping inside transactions.
-
prepareBindingsno longer modifies the caller's list; lost-connection retry loop;getConfig(); driver extensions lookup. -
Empty insert SQL,
Listschema config, migration batches and rollback, PostgreSQL and MySQL introspection,ColumnDiffcomparison. -
Connections: the read connection (
readPdo) is closed bydisconnectand used by the SQL cursor fallback; cancelling a cursor waits for it to close;purge(null)andreconnectclose the old connection;setTablePrefixreaches the schema grammar;ConnectionFactorycloses the write connection when the read one fails, rejects read/write configs with different drivers or no read host, acceptsreadwithoutwrite(and the reverse), several read hosts, a nullprefix, and no longer changes the caller's config map. -
PostgreSQL session:
schemanames with quotes or commas, an emptyschema(noSET search_path TOwith nothing),application_nameandtimezonewith quotes on postgres and postgres_v3. -
SQL:
whereTimeon PostgreSQL (::time, it produced invalid SQL),whereIninside a join,insertUsingwith a CTE on PostgreSQL,whereJsonContainson MySQL, JSON updates on MySQL,onRawin a PostgreSQL update with joins, leadingAND/ORof raw wheres,whereFlexwith a list,get()restores the columns after a failure. -
Migrator: pretend mode without the
pretendkey threw. -
db.raw(value)with a value that is not a String (db.raw(1)) threw aTypeErrorwhen the query was compiled; it is written withtoString(). -
DSNParser.getDSN()always threw (the user, password, port and params were used as bool conditions); it builds the URL again. -
DSNParser.portthrew for a heroku-style URL, whose port is parsed as an int. -
DSNParser:user,password,host,driveranddatabasethrew when the DSN has no such part (always foruserandpasswordof a PDO DSN); they are nullable now. -
DSNParser.paramsthrew for a PDO DSN (its default{}was not aMap<String, dynamic>). -
DSNParser.optionsthrew when the DSN hasoptions=: it is the String written there, not a Map.
Performance #
- About 17% faster
toSql()(direct dispatch andwrap()fast paths) and no per-query column copy inget().
3.4.3 #
- add Postgres whereExists integration test, expand whereExists docs, and include README example for QueryBuilder subquery usage
3.4.2 #
- implemented
insertManyto execute an optimized "batch insert", generating a single SQL statement
3.4.0 #
- upgrade mysql_dart to 1.2.0 for fix textual BLOB/TEXT columns are decoded as UTF‑8 strings consistently
3.3.2 #
Added #
-
JoinClause.onRaw(String sql, [String boolean = 'and', List bindings = const []])— Allows raw SQL fragments insideJOIN ... ONconditions. Use the optionalbooleanto chain withAND/ORandbindingsto safely pass parameters that will be appended to the join bindings.Examples
// Simple raw predicate (defaults to boolean = 'and' and no bindings) db.table('processes') .join('listings as l', (JoinClause jc) { jc.on('l.process_id', '=', 'processes.id'); jc.onRaw("l.state IN ('open','running')"); }) .get(); // Raw predicate with OR and parameter bindings db.table('tickets as t') .join('labels as l', (JoinClause jc) { jc.on('l.ticket_id', '=', 't.id'); jc.onRaw('l.tags @> ARRAY[?]::text[]', 'or', ['urgent']); // adds to join bindings }) .get(); -
QueryBuilder.clone()— Creates a deep, independent copy of a query builder (including selects, joins, wheres, orders, and bindings). Modifying the clone does not affect the original, which is handy for deriving variations from a base query.Example
// 1) Base query for active users final baseQuery = db.table('users').where('status', '=', 'active'); // 2) Independent clones final usersCountQuery = baseQuery.clone(); final firstUserQuery = baseQuery.clone(); // 3) Different uses final totalActiveUsers = await usersCountQuery.count(); final firstUser = await firstUserQuery.orderBy('created_at').first(); // Original remains reusable final allActiveUsers = await baseQuery.get(); -
QueryBuilder.joinRaw(String tableExpression, [List bindings = const [], String type = 'inner', Function(JoinClause)? on])— Adds aJOINusing a raw table/Expression (kept unwrapped), optional parameterbindingsfor that expression, an optional jointype(defaultinner), and an optionaloncallback to configureONconditions (includingonRaw).Examples
// 1) Join a subquery with bindings and configure ON via callback final since = DateTime.now().subtract(const Duration(days: 30)); db.table('orders') .joinRaw( '(select * from invoices where created_at >= ?) as i', [since], // bindings for the tableExpression 'left', (JoinClause jc) { // ON configuration jc.on('i.order_id', '=', 'orders.id'); jc.onRaw('i.status IN (?, ?)', 'and', ['paid', 'settled']); }, ) .get(); // 2) Join a raw expression without ON (e.g., NATURAL or ON TRUE) db.table('metrics') .joinRaw('generate_series(1, 10) as g') .get();
Tests #
- Expanded Postgres pool v2 coverage: concurrency limits (server‑measured),
SET LOCALscoping,lock_timeoutbehavior, connection reuse, and PID churn afterpurge().
3.3.1 #
- fix bug on PgPool for PostgresV2PDO
3.3.0 #
-
add option to decode timestamp without timezone and date as local DateTime and decode timestamp with timezone respecting the timezone defined in the connection for driver_implementation postgres
-
update postgres_v3 to ^3.5.4 With that dependency, upgraded minimum SDK to 3.4
-
change mysql_client to mysql_dart ^1.0.0
-
change postgres_fork ^2.8.4 to postgres_fork ^2.8.5
final manager = Manager();
manager.addConnection({
'driver': 'pgsql',
'driver_implementation': 'postgres_v3', // postgres | dargres | postgres_v3
'timezone': 'America/Sao_Paulo',
// If true, decodes the timestamp with timezone (timestamptz) as UTC = default
// If false, decodes the timestamp with timezone using the timezone defined in the connection.
'forceDecodeTimestamptzAsUTC': false,
// If true, decodes the timestamp without timezone (timestamp) as UTC.
// If false, decodes the timestamp without timezone as local datetime.
'forceDecodeTimestampAsUTC': false,
// If true, decodes the date as UTC.
// If false, decodes the date as local datetime.
'forceDecodeDateAsUTC': false,
'pool': true,
'poolsize': 2,
'host': 'localhost',
'port': '5435',
'database': 'siamweb',
'username': 'dart',
'password': 'dart',
'charset': 'win1252',
'prefix': '',
'schema': ['public'],
// require | disable
//'sslmode' : 'require',
});
3.2.1 #
- fix bug on format Schema
3.2.0 #
- fix bugs in onOpen callback to configure connection settings
- improvements to README
- implemented connection pool for 'postgres' (v2) driver_implementation
3.1.2 #
- fix bugs in lost connection detection to automatically reconnect. Updated postgres to 3.1.2 to be able to use the onOpen callback to configure connection settings like setting search path
3.0.1 #
- fix bug on query builder count()
3.0.0 #
- implemented support for mysql through the 'mysql_client' package and also implemented support for posgress with the postgres v3 package, now you can choose the driver implementation through
'driver_implementation': 'postgres_v3',in addConnection method
2.2.0 #
- add fromRaw (sub-query as from), joinSub (method to join a query to a sub-query)
2.1.2 #
- remove print from disconnect() in Connection class
2.1.1 #
- small improvement and optimization in postgres PDO implementation
2.1.0 #
- change dependencie postgres to my fork postgres_fork
- add more tests
2.0.2 #
- add support to postgres driver https://github.com/insinfo/postgresql-dart-1.git
- type correction and other improvements
2.0.2 #
- fix bug on set application_name to postgresql < 8.2
2.0.1 #
- fixes critical bug in version 2.0.0 that caused stack overflow, timeout parameters were removed from query execution methods
2.0.0 #
- implemented Connection pool with option to automatically reconnect in case of connection drop
final manager = Manager();
manager.addConnection({
'driver': 'pgsql',
'host': 'localhost',
'port': '5432',
'database': 'database',
'username': 'username',
'password': 'password',
'charset': 'latin1',
'schema': ['public'],
'pool': true, //new
'poolsize': 2, //new
'allowreconnect': true, //new
});
1.2.1 #
- fix parameter type of column orWhere
1.2.0 #
- fix bugs and update dargres to 2.2.4
1.1.1 #
- fix bugs on detects_lost_connections
1.1.0 #
- implemented tryAgainIfCausedByLostConnection
1.0.0 #
- Initial version