Skip to content

Migration from QODBC

Both products give you SQL access to a QuickBooks Desktop company file. Syntra is a drop-in replacement for most read workloads and a substantial upgrade for writes — it caches QB data locally, parses full ANSI SQL (JOINs, CTEs, window functions, GROUP BY with HAVING), and removes QODBC’s single-threaded lock on QB’s COM API. This page walks through the switch.

Area QODBC Syntra
Driver surface ODBC only ODBC (bundled) + MCP server for AI tools
Query engine In-process, translates every query to QBFC Local cache (fast reads) + live direct-read fallback
Typical read latency 200 ms – 30 s depending on query shape < 1 ms cached, ~200 ms live
JOINs Supported but QB-SDK-shaped, quirky Full ANSI SQL
Aggregation Partial (SP_REPORT-style) Full GROUP BY / HAVING / window funcs
Subqueries / CTEs Not supported Full support
Concurrency Serialised through a process-wide mutex Concurrent reads; writes serialise only on QB’s own lock
Write batching FQSaveToCache flag per row Multi-row VALUES, grouped by header
Table names PascalCase QB entity names (Customer) snake_case plural (customers)
Column names PascalCase (CustomerRef_FullName) snake_case (customer_ref_full_name)

Follow Installation. The Syntra installer registers its own ODBC driver (Syntra ODBC - QuickBooks ODBC) and a System DSN (Syntra QuickBooks) — QODBC’s driver and DSNs are untouched. You can run both products side-by-side during migration.

Driver={QODBC Driver for QuickBooks};DFQ=C:\path\to\company.QBW;

Using the installer-provided DSN:

DSN=Syntra QuickBooks

Or driver-direct:

Driver={Syntra ODBC - QuickBooks ODBC};Server=127.0.0.1;Port=5433;Database=qbconnect;Uid=qbconnect;Pwd=<your-password>;

Installer defaults for Uid/Pwd come from the [auth] section of config.toml. Change them before production.

See Connection Strings for per-language examples.

QODBC Syntra
Customer customers
Vendor vendors
Employee employees
Item one of item_inventories, item_inventory_assemblies, item_services, item_non_inventories, item_other_charges, item_fixed_assets, item_discounts, item_groups, item_payments, item_sales_taxes, item_sales_tax_groups, item_subtotals
Account accounts
Invoice invoices
InvoiceLine invoice_lines
SalesOrder sales_orders
SalesOrderLine sales_order_lines
PurchaseOrder purchase_orders
PurchaseOrderLine purchase_order_lines
Bill bills
BillExpenseLine expense_lines
BillItemLine item_lines
CreditMemo credit_memos
CreditMemoLine credit_memo_lines
SalesReceipt sales_receipts
SalesReceiptLine sales_receipt_lines
Estimate estimates
EstimateLine estimate_lines
ItemReceipt item_receipts
InventoryAdjustment inventory_adjustments
JournalEntry journal_entries

QODBC’s single Item table splits across twelve item-subtype tables in Syntra. Most Warehouse Traffic / inventory-sync use cases want item_inventories. The whole set shares the same column shape for the fields you’re most likely to use (list_id, name, full_name, sales_price, is_active, custom fields).

Everything goes from PascalCase or PascalCase_Suffix to snake_case.

-- QODBC
SELECT TxnID, CustomerRef_FullName, BalanceRemaining
FROM Invoice
WHERE TxnDate >= {d '2026-01-01'}
-- Syntra
SELECT txn_id, customer_ref_full_name, balance_remaining
FROM invoices
WHERE txn_date >= DATE '2026-01-01'

A few common translations:

QODBC Syntra
CustomerRef_ListID customer_ref_list_id
CustomerRef_FullName customer_ref_full_name
ItemRef_ListID (on line tables) item_ref_list_id
IsFullyInvoiced (SalesOrder) is_fully_invoiced
IsFullyReceived (PurchaseOrder) is_fully_received
IsManuallyClosed is_manually_closed
BalanceRemaining balance_remaining
AverageCost average_cost
QuantityOnHand quantity_on_hand

QODBC accepts ODBC escapes like {d 'YYYY-MM-DD'} and the {ts ...} timestamp escape. Syntra uses standard SQL literals:

-- QODBC
WHERE TxnDate >= {d '2026-01-01'}
-- Syntra
WHERE txn_date >= DATE '2026-01-01'
-- or just
WHERE txn_date >= '2026-01-01'

Stored procedures — SP_REPORT, SP_LASTINSERTID, SP_FQSAVETOCACHEROLLBACK

Section titled “Stored procedures — SP_REPORT, SP_LASTINSERTID, SP_FQSAVETOCACHEROLLBACK”

These are QODBC-specific extensions. Replace them with native SQL:

QODBC construct Syntra equivalent
SP_REPORT OpenPOs show Date, Name as [Vendor], RefNumber, Memo Order by Date Desc SELECT txn_date, vendor_ref_full_name, ref_number, memo FROM purchase_orders WHERE COALESCE(is_fully_received, false) = false AND COALESCE(is_manually_closed, false) = false ORDER BY txn_date DESC
SP_LASTINSERTID <TableName> SELECT txn_id FROM <plural_table> ORDER BY time_created DESC LIMIT 1 — or add RETURNING txn_id to the INSERT
SP_FQSAVETOCACHEROLLBACK off / on Remove — Syntra auto-commits each INSERT
FQSaveToCache flag on multi-line INSERTs Remove — use multi-row VALUES with identical headers (see below)

SELECT ... FROM salesorderline Calldirect WHERE RefNumber = ? becomes plain SELECT ... FROM sales_order_lines WHERE ref_number = ?. The Calldirect keyword is silently dropped; Syntra’s cache layer decides whether to serve from cache or go live (the SET QB_MAX_STALENESS = 0 session setting forces live).

QODBC posts N rows per sales-order / invoice / PO with the FQSaveToCache flag switching to 0 on the last row to commit. Syntra replaces this with multi-row VALUES + identical headers:

-- QODBC (1 row per line, N INSERTs):
-- INSERT INTO SalesOrderLine (...header..., SalesOrderLineItemRefListID, ..., FQSaveToCache) VALUES (..., 1);
-- INSERT INTO SalesOrderLine (...header..., SalesOrderLineItemRefListID, ..., FQSaveToCache) VALUES (..., 1);
-- INSERT INTO SalesOrderLine (...header..., SalesOrderLineItemRefListID, ..., FQSaveToCache) VALUES (..., 0);
-- Syntra (one statement, N lines, grouped into one sales order):
INSERT INTO sales_orders
(customer_ref_list_id, ref_number, po_number, txn_date, memo,
line_item_ref_list_id, line_quantity, line_rate)
VALUES
('80000001-...', 'SO-001', 'PO-1234', DATE '2026-06-15', 'Test',
'80000001-...', 5, 25.00),
('80000001-...', 'SO-001', 'PO-1234', DATE '2026-06-15', 'Test',
'80000002-...', 3, 50.00),
('80000001-...', 'SO-001', 'PO-1234', DATE '2026-06-15', 'Test',
'80000003-...', 10, 10.00);

See INSERT / UPDATE / DELETE for the full pattern including LinkToTxn for bill-from-sales-order flows and journal-entry Debit/Credit dispatch.

QODBC’s TxnDateMacro = 'ThisWeekToDate' magic becomes explicit SQL in Syntra:

QODBC macro Syntra WHERE clause
TxnDateMacro = 'Today' txn_date = CURRENT_DATE
TxnDateMacro = 'Yesterday' txn_date = CURRENT_DATE - INTERVAL '1 day'
TxnDateMacro = 'ThisWeek' / 'ThisWeekToDate' txn_date >= DATE_TRUNC('week', CURRENT_DATE)
TxnDateMacro = 'LastWeek' txn_date >= DATE_TRUNC('week', CURRENT_DATE) - INTERVAL '7 days' AND txn_date < DATE_TRUNC('week', CURRENT_DATE)
TxnDateMacro = 'ThisMonth' / 'ThisMonthToDate' txn_date >= DATE_TRUNC('month', CURRENT_DATE)
TxnDateMacro = 'LastMonth' txn_date >= DATE_TRUNC('month', CURRENT_DATE) - INTERVAL '1 month' AND txn_date < DATE_TRUNC('month', CURRENT_DATE)
TxnDateMacro = 'ThisQuarter' / 'ThisQuarterToDate' txn_date >= DATE_TRUNC('quarter', CURRENT_DATE)
TxnDateMacro = 'ThisYear' / 'ThisYearToDate' txn_date >= DATE_TRUNC('year', CURRENT_DATE)

QODBC surfaces custom fields as PascalCase columns prefixed CustomField (for example CustomFieldUPCCODE). Syntra discovers them automatically at sync time and exposes them as custom_* columns derived from the QB-side name:

-- QODBC
SELECT Name, CustomFieldUPCCODE, CustomFieldWEIGHT FROM ItemInventory
-- Syntra
SELECT name, custom_upccode, custom_weight FROM item_inventories

See Custom Fields for the full naming transform and INSERT/UPDATE support.

Step 4 — remove application-level caching

Section titled “Step 4 — remove application-level caching”

QODBC users often cache query results in-app to avoid paying QBFC latency on every call. Syntra’s built-in cache removes the need — strip that logic.

The cache serves most reads in under a millisecond. If a specific query needs a fresh read straight from QB, set QB_MAX_STALENESS = 0 on the session before running it:

SET QB_MAX_STALENESS = 0;
SELECT * FROM customers WHERE list_id = '80000001-...';

The setting applies for the lifetime of that connection.

Step 5 — drop FQSaveToCache / SP_FQSAVETOCACHEROLLBACK

Section titled “Step 5 — drop FQSaveToCache / SP_FQSAVETOCACHEROLLBACK”

Search your codebase for these tokens and remove them along with the surrounding commit/rollback logic. Syntra auto-commits every INSERT, so the “batch now, commit on the last row” pattern becomes a single multi-row INSERT (see above).

  1. Run your application’s existing test suite against Syntra.
  2. Check the syntra-odbc.log file (under %PROGRAMDATA%\SyntraODBC\logs\) for any warnings about unsupported queries.
  3. Update all connection strings to Syntra.
  4. Remove QODBC from Programs and Features, or unregister the driver from ODBC Data Source Administrator if you want the DLL left in place for rollback.
  • “Column does not exist” — a column was renamed or collapsed. Run DESCRIBE TABLE <name>; to list Syntra’s columns.
  • Query returns far more rows than QODBC did — QODBC often filtered implicitly on IsActive = 1 for list tables. Syntra does not. Add WHERE is_active = true explicitly.
  • Booleans return true/false instead of 0/1 — Syntra returns real booleans. Update any code that compared to 0 or 1 as an integer.
  • SP_LASTINSERTID returned immediately in QODBC — in Syntra, retrieve the new txn_id with SELECT txn_id FROM <table> ORDER BY time_created DESC LIMIT 1 right after the INSERT (on the same connection, before any other writes).
  • Crystal Reports / report-authoring tools.rpt files with embedded QODBC-dialect SQL need to be re-authored against the Syntra schema. The SetConnection(...) call in application code only rebinds the transport; the report’s internal SQL still runs.
  • Multi-line invoice creation from a sales order — the QODBC InvoiceLineLinkToTxnTxnID / InvoiceLineLinkToTxnTxnLineID approach maps to Syntra’s line_link_to_txn_txn_id / line_link_to_txn_txn_line_id virtual columns on invoices. See Linking Invoice Lines to a Sales Order.
  • Receiving a bill or item receipt against a purchase order (since v0.2.25) - the QODBC BillItemLineLinkToTxnTxnID / BillItemLineLinkToTxnTxnLineID approach maps to the same line_link_to_txn_txn_id / line_link_to_txn_txn_line_id columns on bills and item_receipts. See Receiving Against a Purchase Order.