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.
Architectural differences
Section titled “Architectural differences”| 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) |
Step 1 — install Syntra alongside QODBC
Section titled “Step 1 — install Syntra alongside QODBC”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.
Step 2 — update connection strings
Section titled “Step 2 — update connection strings”Before (QODBC)
Section titled “Before (QODBC)”Driver={QODBC Driver for QuickBooks};DFQ=C:\path\to\company.QBW;After (Syntra)
Section titled “After (Syntra)”Using the installer-provided DSN:
DSN=Syntra QuickBooksOr 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.
Step 3 — rewrite SQL
Section titled “Step 3 — rewrite SQL”Table name renames
Section titled “Table name renames”| 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).
Column name renames
Section titled “Column name renames”Everything goes from PascalCase or PascalCase_Suffix to snake_case.
-- QODBCSELECT TxnID, CustomerRef_FullName, BalanceRemainingFROM InvoiceWHERE TxnDate >= {d '2026-01-01'}
-- SyntraSELECT txn_id, customer_ref_full_name, balance_remainingFROM invoicesWHERE 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 |
Date literals
Section titled “Date literals”QODBC accepts ODBC escapes like {d 'YYYY-MM-DD'} and the {ts ...} timestamp escape. Syntra uses standard SQL literals:
-- QODBCWHERE TxnDate >= {d '2026-01-01'}
-- SyntraWHERE txn_date >= DATE '2026-01-01'-- or justWHERE 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) |
Calldirect hints
Section titled “Calldirect hints”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).
Multi-line transaction INSERTs
Section titled “Multi-line transaction INSERTs”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.
{d 'YYYY-MM-DD'} and TxnDateMacro
Section titled “{d 'YYYY-MM-DD'} and TxnDateMacro”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) |
Custom fields
Section titled “Custom fields”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:
-- QODBCSELECT Name, CustomFieldUPCCODE, CustomFieldWEIGHT FROM ItemInventory
-- SyntraSELECT name, custom_upccode, custom_weight FROM item_inventoriesSee 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).
Step 6 — verify and decommission QODBC
Section titled “Step 6 — verify and decommission QODBC”- Run your application’s existing test suite against Syntra.
- Check the
syntra-odbc.logfile (under%PROGRAMDATA%\SyntraODBC\logs\) for any warnings about unsupported queries. - Update all connection strings to Syntra.
- 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.
Common migration gotchas
Section titled “Common migration gotchas”- “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 = 1for list tables. Syntra does not. AddWHERE is_active = trueexplicitly. - Booleans return
true/falseinstead of0/1— Syntra returns real booleans. Update any code that compared to0or1as an integer. SP_LASTINSERTIDreturned immediately in QODBC — in Syntra, retrieve the newtxn_idwithSELECT txn_id FROM <table> ORDER BY time_created DESC LIMIT 1right after the INSERT (on the same connection, before any other writes).- Crystal Reports / report-authoring tools —
.rptfiles with embedded QODBC-dialect SQL need to be re-authored against the Syntra schema. TheSetConnection(...)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/InvoiceLineLinkToTxnTxnLineIDapproach maps to Syntra’sline_link_to_txn_txn_id/line_link_to_txn_txn_line_idvirtual columns oninvoices. See Linking Invoice Lines to a Sales Order.
Further reading
Section titled “Further reading”- INSERT / UPDATE / DELETE — full write syntax including multi-line and LinkToTxn
- Custom Fields
- Special Commands —
SET QB_MAX_STALENESS,CALL qb_void,CALL qb_rebuild_all - Troubleshooting Common Issues