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.

AreaQODBCSyntra
Driver surfaceODBC onlyODBC (bundled) + MCP server for AI tools
Query engineIn-process, translates every query to QBFCLocal cache (fast reads) + live direct-read fallback
Typical read latency200 ms – 30 s depending on query shape< 1 ms cached, ~200 ms live
JOINsSupported but QB-SDK-shaped, quirkyFull ANSI SQL
AggregationPartial (SP_REPORT-style)Full GROUP BY / HAVING / window funcs
Subqueries / CTEsNot supportedFull support
ConcurrencySerialised through a process-wide mutexConcurrent reads; writes serialise only on QB’s own lock
Write batchingFQSaveToCache flag per rowMulti-row VALUES, grouped by header
Table namesPascalCase QB entity names (Customer)snake_case plural (customers)
Column namesPascalCase (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.

QODBCSyntra
Customercustomers
Vendorvendors
Employeeemployees
Itemone 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
Accountaccounts
Invoiceinvoices
InvoiceLineinvoice_lines
SalesOrdersales_orders
SalesOrderLinesales_order_lines
PurchaseOrderpurchase_orders
PurchaseOrderLinepurchase_order_lines
Billbills
BillExpenseLineexpense_lines
BillItemLineitem_lines
CreditMemocredit_memos
CreditMemoLinecredit_memo_lines
SalesReceiptsales_receipts
SalesReceiptLinesales_receipt_lines
Estimateestimates
EstimateLineestimate_lines
ItemReceiptitem_receipts
InventoryAdjustmentinventory_adjustments
JournalEntryjournal_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:

QODBCSyntra
CustomerRef_ListIDcustomer_ref_list_id
CustomerRef_FullNamecustomer_ref_full_name
ItemRef_ListID (on line tables)item_ref_list_id
IsFullyInvoiced (SalesOrder)is_fully_invoiced
IsFullyReceived (PurchaseOrder)is_fully_received
IsManuallyClosedis_manually_closed
BalanceRemainingbalance_remaining
AverageCostaverage_cost
QuantityOnHandquantity_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 constructSyntra equivalent
SP_REPORT OpenPOs show Date, Name as [Vendor], RefNumber, Memo Order by Date DescSELECT 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 / onRemove — Syntra auto-commits each INSERT
FQSaveToCache flag on multi-line INSERTsRemove — 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 macroSyntra 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.