[{"data":1,"prerenderedAt":1322},["ShallowReactive",2],{"navigation_docs_en":3,"-en-database-migrations":342,"-en-database-migrations-surround":1317},[4,45,61,96,132,166,196,217,241,265],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":44},"Getting Started","i-lucide-rocket","\u002Fen\u002Fgetting-started","en\u002F1.getting-started",[10,15,20,24,29,34,39],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fgetting-started\u002Fintroduction","en\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Coming from Laravel","\u002Fen\u002Fgetting-started\u002Fcoming-from-laravel","en\u002F1.getting-started\u002F2.coming-from-laravel","i-lucide-arrow-right-left",{"title":21,"path":22,"stem":23,"icon":19},"Coming from Symfony","\u002Fen\u002Fgetting-started\u002Fcoming-from-symfony","en\u002F1.getting-started\u002F3.coming-from-symfony",{"title":25,"path":26,"stem":27,"icon":28},"Installation","\u002Fen\u002Fgetting-started\u002Finstallation","en\u002F1.getting-started\u002F4.installation","i-lucide-download",{"title":30,"path":31,"stem":32,"icon":33},"Your first module","\u002Fen\u002Fgetting-started\u002Ffirst-module","en\u002F1.getting-started\u002F5.first-module","i-lucide-package-plus",{"title":35,"path":36,"stem":37,"icon":38},"Project structure","\u002Fen\u002Fgetting-started\u002Fproject-structure","en\u002F1.getting-started\u002F6.project-structure","i-lucide-folder-tree",{"title":40,"path":41,"stem":42,"icon":43},"The shop sample application","\u002Fen\u002Fgetting-started\u002Fsample-app","en\u002F1.getting-started\u002F7.sample-app","i-lucide-shopping-cart",false,{"title":46,"icon":47,"path":48,"stem":49,"children":50,"page":44},"Reference","i-lucide-book-open","\u002Fen\u002Freference","en\u002F10.reference",[51,56],{"title":52,"path":53,"stem":54,"icon":55},"Configuration","\u002Fen\u002Freference\u002Fconfiguration","en\u002F10.reference\u002F1.configuration","i-lucide-settings",{"title":57,"path":58,"stem":59,"icon":60},"External dependencies","\u002Fen\u002Freference\u002Fdependencies","en\u002F10.reference\u002F2.dependencies","i-lucide-package",{"title":62,"icon":63,"path":64,"stem":65,"children":66,"page":44},"Concepts","i-lucide-lightbulb","\u002Fen\u002Fconcepts","en\u002F2.concepts",[67,72,77,82,86,91],{"title":68,"path":69,"stem":70,"icon":71},"Architecture","\u002Fen\u002Fconcepts\u002Farchitecture","en\u002F2.concepts\u002F1.architecture","i-lucide-layers",{"title":73,"path":74,"stem":75,"icon":76},"Application lifecycle","\u002Fen\u002Fconcepts\u002Fapplication-lifecycle","en\u002F2.concepts\u002F2.application-lifecycle","i-lucide-power",{"title":78,"path":79,"stem":80,"icon":81},"Error model","\u002Fen\u002Fconcepts\u002Ferror-model","en\u002F2.concepts\u002F3.error-model","i-lucide-shield-alert",{"title":52,"path":83,"stem":84,"icon":85},"\u002Fen\u002Fconcepts\u002Fconfiguration","en\u002F2.concepts\u002F4.configuration","i-lucide-settings-2",{"title":87,"path":88,"stem":89,"icon":90},"Codegen pipeline","\u002Fen\u002Fconcepts\u002Fcodegen-pipeline","en\u002F2.concepts\u002F5.codegen-pipeline","i-lucide-file-json",{"title":92,"path":93,"stem":94,"icon":95},"Design patterns","\u002Fen\u002Fconcepts\u002Fdesign-patterns","en\u002F2.concepts\u002F6.design-patterns","i-lucide-puzzle",{"title":97,"icon":98,"path":99,"stem":100,"children":101,"page":44},"HTTP & Routing","i-lucide-globe","\u002Fen\u002Fhttp","en\u002F3.http",[102,107,112,117,122,127],{"title":103,"path":104,"stem":105,"icon":106},"The server core","\u002Fen\u002Fhttp\u002Fserver","en\u002F3.http\u002F1.server","i-lucide-server",{"title":108,"path":109,"stem":110,"icon":111},"The Fiber engine","\u002Fen\u002Fhttp\u002Ffiber","en\u002F3.http\u002F2.fiber","i-lucide-zap",{"title":113,"path":114,"stem":115,"icon":116},"The chi engine","\u002Fen\u002Fhttp\u002Fchi","en\u002F3.http\u002F3.chi","i-lucide-route",{"title":118,"path":119,"stem":120,"icon":121},"Error responses","\u002Fen\u002Fhttp\u002Ferror-responses","en\u002F3.http\u002F4.error-responses","i-lucide-octagon-alert",{"title":123,"path":124,"stem":125,"icon":126},"Validation","\u002Fen\u002Fhttp\u002Fvalidation","en\u002F3.http\u002F5.validation","i-lucide-badge-check",{"title":128,"path":129,"stem":130,"icon":131},"Pagination","\u002Fen\u002Fhttp\u002Fpagination","en\u002F3.http\u002F6.pagination","i-lucide-list-ordered",{"title":133,"icon":134,"path":135,"stem":136,"children":137,"page":44},"Database","i-lucide-database","\u002Fen\u002Fdatabase","en\u002F4.database",[138,142,147,151,156,161],{"title":139,"path":140,"stem":141,"icon":134},"Overview","\u002Fen\u002Fdatabase\u002Foverview","en\u002F4.database\u002F1.overview",{"title":143,"path":144,"stem":145,"icon":146},"pgx","\u002Fen\u002Fdatabase\u002Fpgx","en\u002F4.database\u002F2.pgx","i-lucide-plug",{"title":148,"path":149,"stem":150,"icon":71},"bun","\u002Fen\u002Fdatabase\u002Fbun","en\u002F4.database\u002F3.bun",{"title":152,"path":153,"stem":154,"icon":155},"Transactions","\u002Fen\u002Fdatabase\u002Ftransactions","en\u002F4.database\u002F4.transactions","i-lucide-git-merge",{"title":157,"path":158,"stem":159,"icon":160},"Migrations","\u002Fen\u002Fdatabase\u002Fmigrations","en\u002F4.database\u002F5.migrations","i-lucide-file-stack",{"title":162,"path":163,"stem":164,"icon":165},"Seeders","\u002Fen\u002Fdatabase\u002Fseeding","en\u002F4.database\u002F6.seeding","i-lucide-sprout",{"title":167,"icon":168,"path":169,"stem":170,"children":171,"page":44},"Async & Background","i-lucide-workflow","\u002Fen\u002Fasync","en\u002F5.async",[172,176,181,186,191],{"title":173,"path":174,"stem":175,"icon":131},"Queue","\u002Fen\u002Fasync\u002Fqueue","en\u002F5.async\u002F1.queue",{"title":177,"path":178,"stem":179,"icon":180},"Events","\u002Fen\u002Fasync\u002Fevents","en\u002F5.async\u002F2.events","i-lucide-radio",{"title":182,"path":183,"stem":184,"icon":185},"Outbox","\u002Fen\u002Fasync\u002Foutbox","en\u002F5.async\u002F3.outbox","i-lucide-inbox",{"title":187,"path":188,"stem":189,"icon":190},"Scheduling","\u002Fen\u002Fasync\u002Fscheduler","en\u002F5.async\u002F4.scheduler","i-lucide-calendar-clock",{"title":192,"path":193,"stem":194,"icon":195},"Worker","\u002Fen\u002Fasync\u002Fworker","en\u002F5.async\u002F5.worker","i-lucide-cog",{"title":197,"icon":198,"path":199,"stem":200,"children":201,"page":44},"Security","i-lucide-shield-check","\u002Fen\u002Fsecurity","en\u002F6.security",[202,207,212],{"title":203,"path":204,"stem":205,"icon":206},"Authentication","\u002Fen\u002Fsecurity\u002Fauthentication","en\u002F6.security\u002F1.authentication","i-lucide-key-round",{"title":208,"path":209,"stem":210,"icon":211},"RBAC","\u002Fen\u002Fsecurity\u002Frbac","en\u002F6.security\u002F2.rbac","i-lucide-users",{"title":213,"path":214,"stem":215,"icon":216},"Policies","\u002Fen\u002Fsecurity\u002Fpolicies","en\u002F6.security\u002F3.policies","i-lucide-gavel",{"title":218,"icon":219,"path":220,"stem":221,"children":222,"page":44},"Observability","i-lucide-activity","\u002Fen\u002Fobservability","en\u002F7.observability",[223,226,231,236],{"title":139,"path":224,"stem":225,"icon":219},"\u002Fen\u002Fobservability\u002Foverview","en\u002F7.observability\u002F1.overview",{"title":227,"path":228,"stem":229,"icon":230},"Logging","\u002Fen\u002Fobservability\u002Flogging","en\u002F7.observability\u002F2.logging","i-lucide-scroll-text",{"title":232,"path":233,"stem":234,"icon":235},"OpenTelemetry","\u002Fen\u002Fobservability\u002Fopentelemetry","en\u002F7.observability\u002F3.opentelemetry","i-lucide-radar",{"title":237,"path":238,"stem":239,"icon":240},"Sentry","\u002Fen\u002Fobservability\u002Fsentry","en\u002F7.observability\u002F4.sentry","i-lucide-bug",{"title":242,"icon":243,"path":244,"stem":245,"children":246,"page":44},"Storage & Mail","i-lucide-hard-drive","\u002Fen\u002Fstorage-mail","en\u002F8.storage-mail",[247,251,256,261],{"title":248,"path":249,"stem":250,"icon":243},"Storage","\u002Fen\u002Fstorage-mail\u002Fstorage","en\u002F8.storage-mail\u002F1.storage",{"title":252,"path":253,"stem":254,"icon":255},"Email","\u002Fen\u002Fstorage-mail\u002Fmail","en\u002F8.storage-mail\u002F2.mail","i-lucide-mail",{"title":257,"path":258,"stem":259,"icon":260},"Notifications","\u002Fen\u002Fstorage-mail\u002Fnotifications","en\u002F8.storage-mail\u002F3.notifications","i-lucide-bell",{"title":262,"path":263,"stem":264,"icon":180},"Realtime","\u002Fen\u002Fstorage-mail\u002Frealtime","en\u002F8.storage-mail\u002F4.realtime",{"title":266,"icon":267,"path":268,"stem":269,"children":270,"page":44},"CLI Reference","i-lucide-terminal","\u002Fen\u002Fcli","en\u002F9.cli",[271,274,279,284,289,294,299,303,308,313,318,323,328,333,337],{"title":139,"path":272,"stem":273,"icon":267},"\u002Fen\u002Fcli\u002Foverview","en\u002F9.cli\u002F1.overview",{"title":275,"path":276,"stem":277,"icon":278},"add db","\u002Fen\u002Fcli\u002Fadd-db","en\u002F9.cli\u002F10.add-db","i-lucide-database-zap",{"title":280,"path":281,"stem":282,"icon":283},"add compose \u002F add docker","\u002Fen\u002Fcli\u002Fadd-compose","en\u002F9.cli\u002F11.add-compose","i-lucide-container",{"title":285,"path":286,"stem":287,"icon":288},"add mail","\u002Fen\u002Fcli\u002Fadd-mail","en\u002F9.cli\u002F12.add-mail","i-lucide-mail-plus",{"title":290,"path":291,"stem":292,"icon":293},"add notification","\u002Fen\u002Fcli\u002Fadd-notification","en\u002F9.cli\u002F13.add-notification","i-lucide-bell-plus",{"title":295,"path":296,"stem":297,"icon":298},"add realtime","\u002Fen\u002Fcli\u002Fadd-realtime","en\u002F9.cli\u002F14.add-realtime","i-lucide-radio-tower",{"title":300,"path":301,"stem":302,"icon":165},"add seeder","\u002Fen\u002Fcli\u002Fadd-seeder","en\u002F9.cli\u002F15.add-seeder",{"title":304,"path":305,"stem":306,"icon":307},"new project","\u002Fen\u002Fcli\u002Fnew-project","en\u002F9.cli\u002F2.new-project","i-lucide-folder-plus",{"title":309,"path":310,"stem":311,"icon":312},"new module","\u002Fen\u002Fcli\u002Fnew-module","en\u002F9.cli\u002F3.new-module","i-lucide-blocks",{"title":314,"path":315,"stem":316,"icon":317},"add surface","\u002Fen\u002Fcli\u002Fadd-surface","en\u002F9.cli\u002F4.add-surface","i-lucide-layers-2",{"title":319,"path":320,"stem":321,"icon":322},"add core","\u002Fen\u002Fcli\u002Fadd-core","en\u002F9.cli\u002F5.add-core","i-lucide-box",{"title":324,"path":325,"stem":326,"icon":327},"add handler","\u002Fen\u002Fcli\u002Fadd-handler","en\u002F9.cli\u002F6.add-handler","i-lucide-webhook",{"title":329,"path":330,"stem":331,"icon":332},"new migration","\u002Fen\u002Fcli\u002Fnew-migration","en\u002F9.cli\u002F7.new-migration","i-lucide-file-plus",{"title":334,"path":335,"stem":336,"icon":195},"worker generators","\u002Fen\u002Fcli\u002Fworker-generators","en\u002F9.cli\u002F8.worker-generators",{"title":338,"path":339,"stem":340,"icon":341},"upgrade templates","\u002Fen\u002Fcli\u002Fupgrade-templates","en\u002F9.cli\u002F9.upgrade-templates","i-lucide-refresh-cw",{"id":343,"title":157,"body":344,"description":1310,"extension":1311,"links":1312,"meta":1313,"navigation":1314,"path":158,"seo":1315,"stem":159,"__hash__":1316},"docs_en\u002Fen\u002F4.database\u002F5.migrations.md",{"type":345,"value":346,"toc":1301},"minimark",[347,369,374,381,391,446,450,460,643,669,672,706,717,723,727,730,1017,1043,1162,1171,1184,1188,1198,1227,1231,1251,1255,1279,1283,1297],[348,349,350,351,355,356,360,361,364,365,368],"p",{},"Schema management in gpsystem is deliberately simple: ",[352,353,354],"strong",{},"timestamp-prefixed SQL files"," in the project's ",[357,358,359],"code",{},"migrations\u002F"," directory, embedded into the binary, with a generated ",[357,362,363],{},"cmd\u002Fmigrate"," runner. There is no schema builder and no external tool to install: migrations run with ",[357,366,367],{},"go run",", like everything else in the project.",[370,371,373],"h2",{"id":372},"what-gets-generated","What gets generated",[348,375,376,377,380],{},"Every new project starts with this (see ",[378,379,304],"a",{"href":305},"):",[382,383,388],"pre",{"className":384,"code":386,"language":387},[385],"language-text","shop\u002F\n├── cmd\u002Fmigrate\u002Fmain.go              # the runner\n└── migrations\u002F\n    ├── embed.go                     # \u002F\u002Fgo:embed *.sql\n    ├── 20200101000000_init.sql      # empty placeholder, where your first schema goes\n    └── 20200101000100_outbox.sql    # the outbox_events table\n","text",[357,389,386],{"__ignoreMap":390},"",[392,393,394,410,434],"ul",{},[395,396,397,402,403,405,406,409],"li",{},[352,398,399],{},[357,400,401],{},"20200101000000_init.sql"," is an empty skeleton, there only so ",[357,404,363],{}," compiles from day one (the embed's ",[357,407,408],{},"*.sql"," pattern expects at least one file). You put your first schema here, or ask the CLI for a new file. The fixed, early timestamp guarantees every later, generation-time-stamped migration sorts after it.",[395,411,412,417,418,421,422,425,426,429,430,433],{},[352,413,414],{},[357,415,416],{},"20200101000100_outbox.sql"," creates the ",[378,419,420],{"href":183},"outbox","'s ",[357,423,424],{},"outbox_events"," table. Its source of truth is the kit's embedded ",[357,427,428],{},"outbox.MigrationSQL"," constant: the schema the relay's queries depend on. For older, pre-worker projects, the ",[357,431,432],{},"add worker"," command writes it in at the next available timestamp.",[395,435,436,441,442,445],{},[352,437,438],{},[357,439,440],{},"embed.go"," is a single ",[357,443,444],{},"\u002F\u002Fgo:embed *.sql"," directive: every migration becomes part of the binary, making the deploy artifact self-contained.",[370,447,449],{"id":448},"the-file-format","The file format",[348,451,452,453,459],{},"Plain SQL, sectioned with ",[378,454,458],{"href":455,"rel":456},"https:\u002F\u002Fgithub.com\u002Fpressly\u002Fgoose",[457],"nofollow","goose"," markers. One file is one migration, with an up and a down direction:",[382,461,466],{"className":462,"code":463,"filename":464,"language":465,"meta":390,"style":390},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","-- +goose Up\n-- +goose StatementBegin\nCREATE TABLE products (\n    id          bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n    name        text        NOT NULL,\n    price_cents bigint      NOT NULL,\n    stock       int         NOT NULL DEFAULT 0,\n    created_at  timestamptz NOT NULL DEFAULT now()\n);\n-- +goose StatementEnd\n\n-- +goose Down\n-- +goose StatementBegin\nDROP TABLE products;\n-- +goose StatementEnd\n","migrations\u002F20260720143012_create_products_and_orders.sql","sql",[357,467,468,477,483,501,529,543,556,576,597,603,609,616,622,627,638],{"__ignoreMap":390},[469,470,473],"span",{"class":471,"line":472},"line",1,[469,474,476],{"class":475},"sHwdD","-- +goose Up\n",[469,478,480],{"class":471,"line":479},2,[469,481,482],{"class":475},"-- +goose StatementBegin\n",[469,484,486,490,493,497],{"class":471,"line":485},3,[469,487,489],{"class":488},"sbssI","CREATE",[469,491,492],{"class":488}," TABLE",[469,494,496],{"class":495},"s2Zo4"," products",[469,498,500],{"class":499},"sTEyZ"," (\n",[469,502,504,507,511,514,517,520,523,526],{"class":471,"line":503},4,[469,505,506],{"class":499},"    id          ",[469,508,510],{"class":509},"spNyl","bigint",[469,512,513],{"class":488}," GENERATED",[469,515,516],{"class":488}," ALWAYS",[469,518,519],{"class":488}," AS",[469,521,522],{"class":488}," IDENTITY",[469,524,525],{"class":509}," PRIMARY KEY",[469,527,528],{"class":499},",\n",[469,530,532,535,538,541],{"class":471,"line":531},5,[469,533,534],{"class":488},"    name",[469,536,537],{"class":509},"        text",[469,539,540],{"class":488},"        NOT NULL",[469,542,528],{"class":499},[469,544,546,549,551,554],{"class":471,"line":545},6,[469,547,548],{"class":499},"    price_cents ",[469,550,510],{"class":509},[469,552,553],{"class":488},"      NOT NULL",[469,555,528],{"class":499},[469,557,559,562,565,568,571,574],{"class":471,"line":558},7,[469,560,561],{"class":499},"    stock       ",[469,563,564],{"class":509},"int",[469,566,567],{"class":488},"         NOT NULL",[469,569,570],{"class":509}," DEFAULT",[469,572,573],{"class":488}," 0",[469,575,528],{"class":499},[469,577,579,582,585,588,590,593],{"class":471,"line":578},8,[469,580,581],{"class":499},"    created_at  ",[469,583,584],{"class":509},"timestamptz",[469,586,587],{"class":488}," NOT NULL",[469,589,570],{"class":509},[469,591,592],{"class":488}," now",[469,594,596],{"class":595},"sMK4o","()\n",[469,598,600],{"class":471,"line":599},9,[469,601,602],{"class":499},");\n",[469,604,606],{"class":471,"line":605},10,[469,607,608],{"class":475},"-- +goose StatementEnd\n",[469,610,612],{"class":471,"line":611},11,[469,613,615],{"emptyLinePlaceholder":614},true,"\n",[469,617,619],{"class":471,"line":618},12,[469,620,621],{"class":475},"-- +goose Down\n",[469,623,625],{"class":471,"line":624},13,[469,626,482],{"class":475},[469,628,630,633,635],{"class":471,"line":629},14,[469,631,632],{"class":488},"DROP",[469,634,492],{"class":488},[469,636,637],{"class":499}," products;\n",[469,639,641],{"class":471,"line":640},15,[469,642,608],{"class":475},[348,644,645,648,649,652,653,656,657,660,661,664,665,668],{},[357,646,647],{},"+goose Up"," \u002F ",[357,650,651],{},"+goose Down"," are the two directions; a ",[357,654,655],{},"StatementBegin","\u002F",[357,658,659],{},"StatementEnd"," pair marks the section between them as a single statement (without it goose splits on semicolons, which matters for multi-line functions and triggers). The filename format is ",[357,662,663],{},"\u003Ctimestamp>_name.sql",": a 14-digit, UTC ",[357,666,667],{},"YYYYMMDDHHMMSS"," timestamp defines the run order and the version. Because it captures the moment of generation rather than a sequence number, it never collides across branches.",[348,670,671],{},"Ask the CLI for a new migration: it assigns the current timestamp and generates the skeleton:",[382,673,677],{"className":674,"code":675,"language":676,"meta":390,"style":390},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go tool gpsystem new migration create_products_and_orders\n# -> migrations\u002F20260720143012_create_products_and_orders.sql\n","sh",[357,678,679,701],{"__ignoreMap":390},[469,680,681,685,689,692,695,698],{"class":471,"line":472},[469,682,684],{"class":683},"sBMFI","go",[469,686,688],{"class":687},"sfazB"," tool",[469,690,691],{"class":687}," gpsystem",[469,693,694],{"class":687}," new",[469,696,697],{"class":687}," migration",[469,699,700],{"class":687}," create_products_and_orders\n",[469,702,703],{"class":471,"line":479},[469,704,705],{"class":475},"# -> migrations\u002F20260720143012_create_products_and_orders.sql\n",[348,707,708,709,712,713,716],{},"Older, sequentially-numbered projects (",[357,710,711],{},"00001_init.sql",", ",[357,714,715],{},"00002_outbox.sql",") do not need to migrate to the new scheme: new migrations still sort correctly after the old ones, since goose orders both formats numerically.",[348,718,719,720,722],{},"Details: ",[378,721,329],{"href":330},".",[370,724,726],{"id":725},"the-cmdmigrate-binary","The cmd\u002Fmigrate binary",[348,728,729],{},"The generated runner is short, and worth reading once in your own project. It is roughly this:",[382,731,735],{"className":732,"code":733,"filename":734,"language":684,"meta":390,"style":390},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","cfg := envconf.MustLoad[config]()             \u002F\u002F loads only the DB_* variables\n\npool := pg.MustNewPool(ctx, cfg.DB)           \u002F\u002F the same pgx pool seeding uses\ndb := stdlib.OpenDBFromPool(pool)             \u002F\u002F database\u002Fsql over the pool\n\ngoose.SetBaseFS(migrations.FS)                \u002F\u002F works off the embedded files\ngoose.SetDialect(\"postgres\")\n\nargs := os.Args[1:]\nif len(args) == 0 {\n    args = []string{\"up\"}                     \u002F\u002F no argument: up\n}\ngoose.RunContext(ctx, args[0], db, \".\", args[1:]...)\n","cmd\u002Fmigrate\u002Fmain.go",[357,736,737,765,769,807,832,836,860,882,886,909,933,963,968],{"__ignoreMap":390},[469,738,739,742,745,748,750,753,756,759,762],{"class":471,"line":472},[469,740,741],{"class":499},"cfg ",[469,743,744],{"class":595},":=",[469,746,747],{"class":499}," envconf",[469,749,722],{"class":595},[469,751,752],{"class":495},"MustLoad",[469,754,755],{"class":595},"[",[469,757,758],{"class":683},"config",[469,760,761],{"class":595},"]()",[469,763,764],{"class":475},"             \u002F\u002F loads only the DB_* variables\n",[469,766,767],{"class":471,"line":479},[469,768,615],{"emptyLinePlaceholder":614},[469,770,771,774,776,779,781,784,787,790,793,796,798,801,804],{"class":471,"line":485},[469,772,773],{"class":499},"pool ",[469,775,744],{"class":595},[469,777,778],{"class":499}," pg",[469,780,722],{"class":595},[469,782,783],{"class":495},"MustNewPool",[469,785,786],{"class":595},"(",[469,788,789],{"class":499},"ctx",[469,791,792],{"class":595},",",[469,794,795],{"class":499}," cfg",[469,797,722],{"class":595},[469,799,800],{"class":499},"DB",[469,802,803],{"class":595},")",[469,805,806],{"class":475},"           \u002F\u002F the same pgx pool seeding uses\n",[469,808,809,812,814,817,819,822,824,827,829],{"class":471,"line":503},[469,810,811],{"class":499},"db ",[469,813,744],{"class":595},[469,815,816],{"class":499}," stdlib",[469,818,722],{"class":595},[469,820,821],{"class":495},"OpenDBFromPool",[469,823,786],{"class":595},[469,825,826],{"class":499},"pool",[469,828,803],{"class":595},[469,830,831],{"class":475},"             \u002F\u002F database\u002Fsql over the pool\n",[469,833,834],{"class":471,"line":531},[469,835,615],{"emptyLinePlaceholder":614},[469,837,838,840,842,845,847,850,852,855,857],{"class":471,"line":545},[469,839,458],{"class":499},[469,841,722],{"class":595},[469,843,844],{"class":495},"SetBaseFS",[469,846,786],{"class":595},[469,848,849],{"class":499},"migrations",[469,851,722],{"class":595},[469,853,854],{"class":499},"FS",[469,856,803],{"class":595},[469,858,859],{"class":475},"                \u002F\u002F works off the embedded files\n",[469,861,862,864,866,869,871,874,877,879],{"class":471,"line":558},[469,863,458],{"class":499},[469,865,722],{"class":595},[469,867,868],{"class":495},"SetDialect",[469,870,786],{"class":595},[469,872,873],{"class":595},"\"",[469,875,876],{"class":687},"postgres",[469,878,873],{"class":595},[469,880,881],{"class":595},")\n",[469,883,884],{"class":471,"line":578},[469,885,615],{"emptyLinePlaceholder":614},[469,887,888,891,893,896,898,901,903,906],{"class":471,"line":599},[469,889,890],{"class":499},"args ",[469,892,744],{"class":595},[469,894,895],{"class":499}," os",[469,897,722],{"class":595},[469,899,900],{"class":499},"Args",[469,902,755],{"class":595},[469,904,905],{"class":488},"1",[469,907,908],{"class":595},":]\n",[469,910,911,915,918,920,923,925,928,930],{"class":471,"line":605},[469,912,914],{"class":913},"s7zQu","if",[469,916,917],{"class":495}," len",[469,919,786],{"class":595},[469,921,922],{"class":499},"args",[469,924,803],{"class":595},[469,926,927],{"class":595}," ==",[469,929,573],{"class":488},[469,931,932],{"class":595}," {\n",[469,934,935,938,941,944,947,950,952,955,957,960],{"class":471,"line":611},[469,936,937],{"class":499},"    args ",[469,939,940],{"class":595},"=",[469,942,943],{"class":595}," []",[469,945,946],{"class":509},"string",[469,948,949],{"class":595},"{",[469,951,873],{"class":595},[469,953,954],{"class":687},"up",[469,956,873],{"class":595},[469,958,959],{"class":595},"}",[469,961,962],{"class":475},"                     \u002F\u002F no argument: up\n",[469,964,965],{"class":471,"line":618},[469,966,967],{"class":595},"}\n",[469,969,970,972,974,977,979,981,983,986,988,991,994,997,999,1002,1004,1006,1008,1010,1012,1014],{"class":471,"line":624},[469,971,458],{"class":499},[469,973,722],{"class":595},[469,975,976],{"class":495},"RunContext",[469,978,786],{"class":595},[469,980,789],{"class":499},[469,982,792],{"class":595},[469,984,985],{"class":499}," args",[469,987,755],{"class":595},[469,989,990],{"class":488},"0",[469,992,993],{"class":595},"],",[469,995,996],{"class":499}," db",[469,998,792],{"class":595},[469,1000,1001],{"class":595}," \"",[469,1003,722],{"class":687},[469,1005,873],{"class":595},[469,1007,792],{"class":595},[469,1009,985],{"class":499},[469,1011,755],{"class":595},[469,1013,905],{"class":488},[469,1015,1016],{"class":595},":]...)\n",[348,1018,1019,1020,1026,1027,1030,1031,1034,1035,1038,1039,1042],{},"That is: it loads the ",[378,1021,1023],{"href":1022},"\u002Fen\u002Fdatabase\u002Foverview#config",[357,1024,1025],{},"dbx.Config"," from the ",[357,1028,1029],{},"DB_*"," env variables (the same ones the app uses), and runs goose ",[352,1032,1033],{},"as a library"," over the embedded filesystem, on top of a single pgx pool (the same one seeding opens too, when you call it with ",[357,1036,1037],{},"--seed"," or the ",[357,1040,1041],{},"seed"," subcommand). The command passes straight through to goose:",[382,1044,1046],{"className":674,"code":1045,"language":676,"meta":390,"style":390},"go run .\u002Fcmd\u002Fmigrate              # = up: all pending migrations, in order\ngo run .\u002Fcmd\u002Fmigrate up\ngo run .\u002Fcmd\u002Fmigrate down         # roll back the most recent migration\ngo run .\u002Fcmd\u002Fmigrate status       # which files ran, which are pending\ngo run .\u002Fcmd\u002Fmigrate version      # current schema version\ngo run .\u002Fcmd\u002Fmigrate up-to 3      # up to a given version (the number as an int)\ngo run .\u002Fcmd\u002Fmigrate up --seed    # migrate, then run the seeders\ngo run .\u002Fcmd\u002Fmigrate seed         # run the seeders only, no migration\n",[357,1047,1048,1061,1072,1086,1100,1114,1131,1148],{"__ignoreMap":390},[469,1049,1050,1052,1055,1058],{"class":471,"line":472},[469,1051,684],{"class":683},[469,1053,1054],{"class":687}," run",[469,1056,1057],{"class":687}," .\u002Fcmd\u002Fmigrate",[469,1059,1060],{"class":475},"              # = up: all pending migrations, in order\n",[469,1062,1063,1065,1067,1069],{"class":471,"line":479},[469,1064,684],{"class":683},[469,1066,1054],{"class":687},[469,1068,1057],{"class":687},[469,1070,1071],{"class":687}," up\n",[469,1073,1074,1076,1078,1080,1083],{"class":471,"line":485},[469,1075,684],{"class":683},[469,1077,1054],{"class":687},[469,1079,1057],{"class":687},[469,1081,1082],{"class":687}," down",[469,1084,1085],{"class":475},"         # roll back the most recent migration\n",[469,1087,1088,1090,1092,1094,1097],{"class":471,"line":503},[469,1089,684],{"class":683},[469,1091,1054],{"class":687},[469,1093,1057],{"class":687},[469,1095,1096],{"class":687}," status",[469,1098,1099],{"class":475},"       # which files ran, which are pending\n",[469,1101,1102,1104,1106,1108,1111],{"class":471,"line":531},[469,1103,684],{"class":683},[469,1105,1054],{"class":687},[469,1107,1057],{"class":687},[469,1109,1110],{"class":687}," version",[469,1112,1113],{"class":475},"      # current schema version\n",[469,1115,1116,1118,1120,1122,1125,1128],{"class":471,"line":545},[469,1117,684],{"class":683},[469,1119,1054],{"class":687},[469,1121,1057],{"class":687},[469,1123,1124],{"class":687}," up-to",[469,1126,1127],{"class":488}," 3",[469,1129,1130],{"class":475},"      # up to a given version (the number as an int)\n",[469,1132,1133,1135,1137,1139,1142,1145],{"class":471,"line":558},[469,1134,684],{"class":683},[469,1136,1054],{"class":687},[469,1138,1057],{"class":687},[469,1140,1141],{"class":687}," up",[469,1143,1144],{"class":687}," --seed",[469,1146,1147],{"class":475},"    # migrate, then run the seeders\n",[469,1149,1150,1152,1154,1156,1159],{"class":471,"line":578},[469,1151,684],{"class":683},[469,1153,1054],{"class":687},[469,1155,1057],{"class":687},[469,1157,1158],{"class":687}," seed",[469,1160,1161],{"class":475},"         # run the seeders only, no migration\n",[348,1163,1164,1165,1170],{},"Goose tracks applied migrations in the ",[352,1166,1167],{},[357,1168,1169],{},"goose_db_version"," table (created on first run). Each migration file runs in a transaction: if one statement fails, all statements of that file roll back and the version table does not advance.",[348,1172,1173,656,1175,1177,1178,1181,1182,722],{},[357,1174,1037],{},[357,1176,1041],{}," runs the project's central ",[357,1179,1180],{},"seeds\u002F"," package; see ",[378,1183,162],{"href":163},[370,1185,1187],{"id":1186},"why-no-external-migration-tool","Why no external migration tool?",[348,1189,1190,1191,1194,1195,1197],{},"The stack has no golang-migrate, no atlas, and you ",[352,1192,1193],{},"do not install goose's CLI either",": goose runs purely as a Go dependency, compiled into your own ",[357,1196,363],{}," binary. Three consequences:",[1199,1200,1201,1211,1221],"ol",{},[395,1202,1203,1206,1207,1210],{},[352,1204,1205],{},"The deploy artifact is self-contained."," Migrations are embedded into the binary; CI and the production image copy no SQL files and carry no extra tool. The output of ",[357,1208,1209],{},"go build .\u002Fcmd\u002Fmigrate"," carries everything.",[395,1212,1213,1216,1217,1220],{},[352,1214,1215],{},"No version drift."," The migration runner comes from the same ",[357,1218,1219],{},"go.mod"," as everything else: CI cannot run a different goose version than a developer machine.",[395,1222,1223,1226],{},[352,1224,1225],{},"Plain SQL is the source of truth."," No DSL and no diff-based magic: what is in the file is what runs, and code review sees exactly that.",[370,1228,1230],{"id":1229},"named-connections-are-not-migrated","Named connections are not migrated",[348,1232,1233,1235,1236,1238,1239,1243,1244,1246,1247,1250],{},[357,1234,363],{}," only ever loads the project's default ",[357,1237,1029],{}," connection: it has no notion of the ",[378,1240,1242],{"href":1241},"\u002Fen\u002Fdatabase\u002Foverview#named-connections","named connections"," ",[357,1245,275],{}," adds to a module. Goose's version table is per-database, and a named connection often points at something the service does not own the schema of (a warehouse, a read replica, a legacy database), so there is no generated runner for them. If a named connection does need its own migrated schema, run goose against it by hand or add a second ",[357,1248,1249],{},"cmd\u002Fmigrate-\u003Cname>"," binary following the same pattern as the generated one.",[370,1252,1254],{"id":1253},"deployment","Deployment",[392,1256,1257,1267,1273],{},[395,1258,1259,1262,1263,1266],{},[352,1260,1261],{},"Run migrations before the app, as a separate step",": an init container, a release-phase command or a deploy pipeline step: ",[357,1264,1265],{},"go run .\u002Fcmd\u002Fmigrate up"," (or the prebuilt binary). The app process does not run migrations on startup.",[395,1268,1269,1272],{},[352,1270,1271],{},"One runner at a time."," Migrations should run as a single step of the deploy, not on every replica in parallel, with multiple replicas, prefer a pipeline step over the init-container-per-pod pattern.",[395,1274,1275,1278],{},[352,1276,1277],{},"Forward-compatible schema."," During a rolling deploy the old app version is still running while the new schema is already live. Additive changes (new table, new nullable column) just work; split column drops and renames across two deploys.",[370,1280,1282],{"id":1281},"no-schema-builder","No schema builder",[348,1284,1285,1286,1289,1290,1292,1293,1296],{},"gpsystem deliberately has no schema builder: migrations are plain SQL. A builder buys portability across databases, which is not needed here (the kit is built on PostgreSQL), at the cost of hiding what DDL actually runs. Plain SQL gives you PostgreSQL's full surface (partial indexes, ",[357,1287,1288],{},"GENERATED"," columns, CTE-based backfills), exactly as the database documentation describes them. ",[357,1291,416],{}," is a good example: a partial index (",[357,1294,1295],{},"WHERE published_at IS NULL",") on unpublished rows, expressed directly and without an abstraction layer in between.",[1298,1299,1300],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}",{"title":390,"searchDepth":479,"depth":479,"links":1302},[1303,1304,1305,1306,1307,1308,1309],{"id":372,"depth":479,"text":373},{"id":448,"depth":479,"text":449},{"id":725,"depth":479,"text":726},{"id":1186,"depth":479,"text":1187},{"id":1229,"depth":479,"text":1230},{"id":1253,"depth":479,"text":1254},{"id":1281,"depth":479,"text":1282},"Timestamp-prefixed SQL files embedded into the binary, with a generated cmd\u002Fmigrate runner, no external tool.","md",null,{},{"icon":160},{"title":157,"description":1310},"b7ateXqesFGJoAzop0da8T_ArhGCOs4Aaw2Ueaaj5sM",[1318,1320],{"title":152,"path":153,"stem":154,"description":1319,"icon":155,"children":-1},"Cross-repository transactions carried by the context, no plumbing.",{"title":162,"path":163,"stem":164,"description":1321,"icon":165,"children":-1},"Idempotent baseline data loading after migrations, built on first-or-create, with optional guards and multi-tenant support.",1784668709574]