# Facing Unterminated dollar quote started at position 3 in SQL DO $$ for postgresql migration

**URL:** <https://forum.liquibase.org/t/facing-unterminated-dollar-quote-started-at-position-3-in-sql-do-for-postgresql-migration/8716>\
**Category:** General Discussion\
**Created:** [October 6, 2023, 11:36pm UTC](https://forum.liquibase.org/t/facing-unterminated-dollar-quote-started-at-position-3-in-sql-do-for-postgresql-migration/8716 "2023-10-06T23:36:55Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![springland](https://avatars.discourse-cdn.com/v4/letter/s/a698b9/32.png) [@springland](https://forum.liquibase.org/u/springland)\
**Post date:** [October 6, 2023, 11:36pm UTC](https://forum.liquibase.org/t/facing-unterminated-dollar-quote-started-at-position-3-in-sql-do-for-postgresql-migration/8716/1 "2023-10-06T23:36:55Z")

</div>

Hello,

I am struggling with an unterminated dollar quote error when I did a postgresql migration

The SQL is very simple , The below runs well when I connect to Postgresql with UI tool , however when I put in it liquibase I got unterminated dollar quote error. Per the stack trace the error was thrown by postgresql , since it works well on UI I am wondering if liquibase chunk the SQL at the first ; instead of sending everything between $$?

DO $$  
DECLARE  
var\_timeseries\_id BIGINT ;  
var\_constituent\_timeseries\_id BIGINT ;  
BEGIN

END $$;

Caused by: liquibase.exception.DatabaseException: Unterminated dollar quote started at position 3 in SQL DO $$  
DECLARE  
var\_timeseries\_id BIGINT. Expected terminating $$ [Failed SQL: (0) DO $$  
DECLARE  
var\_timeseries\_id BIGINT]  
at liquibase.executor.jvm.JdbcExecutor$ExecuteStatementCallback.doInStatement(JdbcExecutor.java:445)  
at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:77)  
at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:160)  
at liquibase.database.AbstractJdbcDatabase.execute(AbstractJdbcDatabase.java:1270)  
at liquibase.database.AbstractJdbcDatabase.executeStatements(AbstractJdbcDatabase.java:1252)  
at liquibase.changelog.ChangeSet.execute(ChangeSet.java:702)  
… 56 common frames omitted  
Caused by: org.postgresql.util.PSQLException: Unterminated dollar quote started at position 3 in SQL DO $$  
DECLARE  
var\_timeseries\_id BIGINT. Expected terminating $$  
at org.postgresql.core.Parser.checkParsePosition(Parser.java:1443)  
at org.postgresql.core.Parser.parseSql(Parser.java:1342)  
at org.postgresql.core.Parser.replaceProcessing(Parser.java:1294)  
at org.postgresql.core.CachedQueryCreateAction.create(CachedQueryCreateAction.java:43)  
at org.postgresql.core.QueryExecutorBase.createQueryByKey(QueryExecutorBase.java:369)  
at org.postgresql.jdbc.PgStatement.executeCachedSql(PgStatement.java:313)  
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:297)  
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:292)  
at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:94)  
at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java)  
at liquibase.executor.jvm.JdbcExecutor$ExecuteStatementCallback.doInStatement(JdbcExecutor.java:441)  
… 61 common frames omitted

---

<div class="post-metadata">

**Author:** ![daryldoak](https://sea2.discourse-cdn.com/flex020/user_avatar/forum.liquibase.org/daryldoak/32/764_2.png) [@daryldoak](https://forum.liquibase.org/u/daryldoak)\
**Post date:** [October 10, 2023, 1:28pm UTC](https://forum.liquibase.org/t/facing-unterminated-dollar-quote-started-at-position-3-in-sql-do-for-postgresql-migration/8716/2 "2023-10-10T13:28:35Z")

</div>

The default statement delimiter in Liquibase is the semicolon. If you want $$ to be your statement delimiter, you need to set the “endDelimiter” for the changeset.

> **[endDelimiter SQL attribute](https://docs.liquibase.com/change-types/enddelimiter-sql.html)**
>
> Reference information for the endDelimiter SQL attribute. The endDelimiter SQL attribute is generally used to prevent incomplete SQL statements from being processed when the SQL files contain objects referencing a ';' which Liquibase treats as the...

---

<div class="post-metadata">

**Author:** ![crb](https://avatars.discourse-cdn.com/v4/letter/c/ce7236/32.png) [@crb](https://forum.liquibase.org/u/crb)\
**Post date:** [May 27, 2025, 4:23pm UTC](https://forum.liquibase.org/t/facing-unterminated-dollar-quote-started-at-position-3-in-sql-do-for-postgresql-migration/8716/3 "2025-05-27T16:23:21Z")

</div>

Did this solution work?

---

<div class="post-metadata">

**Author:** ![klitos](https://avatars.discourse-cdn.com/v4/letter/k/258eb7/32.png) [@klitos](https://forum.liquibase.org/u/klitos)\
**Post date:** [February 15, 2026, 1:37pm UTC](https://forum.liquibase.org/t/facing-unterminated-dollar-quote-started-at-position-3-in-sql-do-for-postgresql-migration/8716/4 "2026-02-15T13:37:13Z")

</div>

I came here with the same question. No, the above answer is not useful. The `$$` symbol is not being used as a statement delimiter. It is used instead of a quotation mark. See [dollar-quoted strings](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-DOLLAR-QUOTING). It looks like a bug in Liquibase in the sense that it doesn’t recognise the `$$` symbol as a means of quoting a string literal, so it looks at a `;` in the middle of the string literal and thinks it terminates a statement. There are workarounds such as [changing the endDelimiter](https://support.liquibase.com/hc/en-us/articles/29383025767323--Error-Unterminated-dollar-quote) from a `;` to something else.

That might work for some, but if you have many other statements in the same changeset, this workaround is not ideal.

---

<div class="post-metadata">

**Author:** ![crb](https://avatars.discourse-cdn.com/v4/letter/c/ce7236/32.png) [@crb](https://forum.liquibase.org/u/crb)\
**Post date:** [February 17, 2026, 4:07pm UTC](https://forum.liquibase.org/t/facing-unterminated-dollar-quote-started-at-position-3-in-sql-do-for-postgresql-migration/8716/5 "2026-02-17T16:07:06Z")

</div>

We addressed this issue by implementing a SQL file change set. This approach bypasses the interpreter and sends the file directly to the RDBMS for parsing.

- changeSet:  
id: CREATE  
author: dba  
comment: CREATE Script  
changes:
  - sqlFile:  
dbms: ‘postgresql’  
splitStatements: false  
path: src/main/resources/db-scripts/app1/v2/ddl.sql
