Documentation
¶
Overview ¶
Package dump builds and writes TableX's database exports.
It holds the whole dump engine: the per-schema planner, the cross-schema dependency graph with its cycle resolution and drop-first teardown, and the SQL/CSV/JSON writers. None of it imports net/http — it used to live inside the HTTP handler package purely because that is where the export route is, which put ~1,800 lines of planning and writing behind a request handler.
The handler now does only what a handler does: parse the form, resolve the scope, open the export connection, and hand this package a plan to write.
Index ¶
- Constants
- func CommentSafe(s string) string
- func IsBinaryColumn(c model.Column) bool
- func PlanEmpty(p driver.DumpPlan) bool
- func UnescapeCSVCell(s string) string
- func WriteCSV(ctx context.Context, w io.Writer, conn *driver.Connection, plan []CSVTable) error
- func WriteDB(ctx context.Context, w io.Writer, conn *driver.Connection, dbp *DBPlan, ...)
- func WriteJSON(ctx context.Context, w io.Writer, conn *driver.Connection, ...)
- func WritePostamble(w io.Writer, d driver.Dialect)
- func WritePreamble(w io.Writer, d driver.Dialect)
- func WriteXML(ctx context.Context, w io.Writer, conn *driver.Connection, ...)
- type CSVTable
- type DBPlan
- type Options
- type Plan
- type RowFilter
- type RowRange
- type SchemaGroup
- type Section
Constants ¶
const CSVNullSentinel = `\N`
CSVNullSentinel is the textual marker a CSV cell carries for SQL NULL, distinguishing it from an empty string. It is the de-facto industry marker (MySQL LOAD DATA, PostgreSQL text COPY, Snowflake, Redshift, Hive all use \N). A literal value that begins with a backslash is escaped on export and un-escaped on import so it can never collide with the sentinel.
Variables ¶
This section is empty.
Functions ¶
func CommentSafe ¶
CommentSafe maps every C0 control character (and DEL) in s to a space so an object name or error string embedded in a "-- " / "# " dump comment cannot smuggle an executable statement onto the next line via a newline (a quoted SQL identifier may legally contain newlines/semicolons — the mysqldump CVE-2016-5483 class). Lossy scrubbing is safe: the round-tripping DDL uses QuoteIdent, which preserves the true name.
func IsBinaryColumn ¶
IsBinaryColumn reports whether the column holds raw binary bytes (CSV/JSON export emits these as lowercase hex; CSV import hex-decodes them). The CSV importer consults it too — edit-form binary cells are read-only, so coerceValue never sees them.
It is deliberately kept separate from driver.isBinaryDBType (NOT merged): this works on the normalized model.Column.BaseType (exact match), while the driver helper uppercases an arbitrary raw DB-type string and adds a Contains("BLOB"/"BINARY") substring fallback this one doesn't need. Different inputs, different matching — a single Column-only method couldn't serve the driver's raw-string caller.
func PlanEmpty ¶
PlanEmpty reports whether a DumpPlan carries nothing to emit — the global-object section is only added when a dialect actually produced globals, so engines without a GlobalDumper keep byte-identical output.
func UnescapeCSVCell ¶
UnescapeCSVCell reverses escapeCSVCell. The exact \N sentinel is handled by the caller (→ NULL) before this; any remaining backslash-leading cell was escaped on export, so strip one leading backslash.
func WriteCSV ¶
WriteCSV streams the given tables as CSV. Each table is emitted sequentially, phpMyAdmin-style: a "# <table>" comment line, that table's header row, then its data rows, with a blank line separating consecutive tables.
Cell values are written near-verbatim: binary as lowercase hex, NULL as the \N sentinel (so it is distinct from an empty string on re-import), and a literal value that begins with a backslash gets one escaping backslash. We deliberately do NOT neutralize "CSV formula injection" (cells beginning with = + - @ tab/CR that a spreadsheet may evaluate on open): the only safe neutralization mutates the data (prefixing a quote/tab), which would corrupt a faithful export and break TableX's own CSV re-import round-trip. The risk lives in the spreadsheet that opens the file, not in TableX; it is documented in docs/security.md so the operator can decide. This matches phpMyAdmin, which exports values unaltered.
func WriteDB ¶
WriteDB writes one database's resolved dump: schemas, warnings, teardown (post-data constraint drops + the reversed pre-data stream, so no DROP is blocked by a dependent in any schema), the topo-ordered pre-data stream, the data phase (a hard barrier), then post-data in postDataRank buckets with cross-schema matview REFRESHes topo-ordered by ViewEdges. Data-phase errors become in-dump comments plus a trailing summary; structure was preflighted (ResolveDB) and cannot fail here.
func WriteJSON ¶
func WriteJSON(ctx context.Context, w io.Writer, conn *driver.Connection, groups []SchemaGroup, nested bool, filter *RowFilter, rng *RowRange)
WriteJSON streams tables as a JSON object. Schema-less engines emit a flat { "table": [ {col: value}, ... ] }; schema-having engines nest by schema — { "schema": { "table": [ ... ] } } — so same-named tables in different schemas never collide.
func WritePreamble ¶
WritePreamble pins the session state a restore needs; the matching WritePostamble restores it for sessions that outlive the script. What each engine pins lives with its Dumper (a non-Dumper dialect needs no session pinning).
func WriteXML ¶
func WriteXML(ctx context.Context, w io.Writer, conn *driver.Connection, groups []SchemaGroup, nested bool, filter *RowFilter, rng *RowRange)
WriteXML streams tables as an XML document.
Shape — schema-less engines omit the <schema> level:
<?xml version="1.0" encoding="utf-8"?>
<tablex_export version="1">
<schema name="public">
<table name="orders">
<row>
<column name="id">1</column>
<column name="note" null="true"/>
<column name="blob" format="hex">deadbeef</column>
</row>
</table>
</schema>
</tablex_export>
A column's NAME is an attribute, never an element name. That is not a style choice: an identifier may legally contain spaces, quotes, dots and leading digits, none of which are valid in an XML element name, so <my column> would be an unparseable document. The same reasoning applies to schema and table.
Like JSON this is EXPORT-ONLY — the importer takes SQL and CSV — so all columns are included (the generated-column filter exists to keep CSV re-importable, and that rationale does not apply here).
Types ¶
type CSVTable ¶
type CSVTable struct {
// contains filtered or unexported fields
}
CSVTable is one table's preflighted CSV state.
func BuildCSVPlan ¶
func BuildCSVPlan(ctx context.Context, conn *driver.Connection, tables []driver.TableRef, filter *RowFilter, rng *RowRange) ([]CSVTable, error)
BuildCSVPlan resolves each table's exportable (non-generated) column list and SELECT up front, BEFORE the download headers go out, so a column-introspection failure is a clean rendered error instead of a corrupt, already-committed download. The all-generated degenerate case is a rendered preflight error for a single-table export; in a multi-table export the table is skipped with an explicit comment instead (WriteCSV), since failing the whole download over one degenerate table would lose every other table.
Generated columns are excluded: a CSV whose header carries one is unimportable by construction (importCSV refuses generated headers), so an explicit non-generated column list is used instead of SELECT *. (JSON keeps all columns — it is export-only, so the importability rationale does not apply and dropping generated values would be silent loss.)
type DBPlan ¶
type DBPlan struct {
// contains filtered or unexported fields
}
DBPlan is one database's fully resolved SQL dump: the input sections (data + post-data still stream from them), the topo-ordered pre-data stream, the deferred post-data scripts cycle resolution produced, and — for a drop-first dump — the resolved teardown.
func ResolveDB ¶
func ResolveDB(ctx context.Context, conn *driver.Connection, sections []Section, o Options) (*DBPlan, error)
ResolveDB runs the C3 cycle resolution over one database's sections and returns the resolved emission plan. Resolution rounds mirror the plan's escalation: (1) CUT deferrable edges (a domain's DEFAULT/CHECK clause, a table column DEFAULT or validated CHECK/EXCLUDE expression) whose edges close a cycle — the clause re-emerges as deferred DDL in its lane; (2) STAGE routines still cyclic (stub now, CREATE OR REPLACE "-final" later — the routine analog of type-shell/type-final); (3) any remaining cycle preflight-FAILS with a precise error (a warning would leave a silently broken dump). External dependers of a staged/deferred object are retargeted to its "-final" stage; members of the SAME original cycle keep the base id (retargeting them would re-close the cycle).
type Options ¶
type Options struct {
Structure bool
Data bool
DropFirst bool
// Rows restricts the data phase to specific rows of ONE table (the browse
// grid's "with selected" export). Nil for an ordinary whole-table dump.
Rows *RowFilter
// Range bounds the data phase to a slice of the table's rows, for sampling
// something too big to export whole. Nil for an unbounded dump.
Range *RowRange
}
Options are the user's POSTed dump choices.
type Plan ¶
type Plan struct {
// contains filtered or unexported fields
}
Plan holds everything introspected up front: all structure DDL is small strings, so collecting it before the download headers go out turns an introspection failure into a rendered error instead of a silently incomplete download. Only row data streams afterwards.
func BuildPlan ¶
func BuildPlan(ctx context.Context, conn *driver.Connection, sc driver.TableRef, tables []driver.TableRef, level string, o Options, target *model.Table) (*Plan, error)
BuildPlan preflights one schema's SQL dump: it resolves what to emit and collects every piece of structure DDL BEFORE the download headers go out. Only row data streams afterwards, so an introspection failure is a rendered error rather than a silently truncated file.
The work is three stages, each its own function: route a special target (view / foreign table), read the catalog once, then build one tableDump per table.
type RowFilter ¶
RowFilter restricts a dump's data phase to specific rows of one table: a parameterized WHERE body (no leading "WHERE") plus the values to bind.
It names its target table on purpose. A dump walks a table LIST, and a filter built from one grid's row keys names key columns that a sibling table need not even have — so "apply it to whatever table comes next" is either an error or, far worse, a silent match on rows the user never selected. clauseFor is the single place that decision is made, and its answer for a non-target table is "no data", never "all data".
type RowRange ¶
RowRange is a LIMIT/OFFSET over a table's rows.
A caveat worth stating rather than hiding: SQL gives an unordered SELECT no defined row order, so an OFFSET is only meaningful relative to whatever order the engine happened to use for the same query moments earlier. "First N rows" is dependable; "rows 1000-2000, twice" is not. Adding an ORDER BY to make it so would put a sort on every export of every large table, which is a steep price for a sampling aid — so the export form says this instead.
type SchemaGroup ¶
SchemaGroup is the (non-view) tables of one schema to export. Schema is "" for schema-less engines (MySQL/SQLite).