[
  {
    "path": ".circleci/config.yml",
    "content": "version: 2.1\n\norbs:\n  ta-go: travelaudience/go@0.9\n\nexecutors:\n  golang-executor:\n    docker:\n    - image: cimg/go:1.23\n\nworkflows:\n  build_and_test:\n    jobs:\n\n    - ta-go/checks:\n        name: check\n        exec: golang-executor\n        run-static-analysis: true\n\n    - ta-go/test_and_coverage:\n        name: test\n        exec: golang-executor\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# travel audience Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, gender identity and expression, level of experience,\nnationality, personal appearance, race, religion, or sexual identity and\norientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\nadvances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n### Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at opensource@travelaudience.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at [https://contributor-covenant.org/version/1/4][version]\n\n[homepage]: https://contributor-covenant.org\n[version]: https://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "<!--- STOP! Before you open an issue please search this repository's issues to see if it has already been reported. This helps reduce duplicate issues from being created. -->\n\n### Expected Behaviour\n\n### Actual Behaviour\n\n### Steps to Reproduce\n\n\n### Optional additional info:\n\n#### Platform and Version\n\n#### Sample Code that illustrates the problem\n\n#### Logs taken while reproducing problem\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--  Thanks for sending a pull request!  Here are some tips for you:\nIf this PR closes another issue, add 'closes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'refs #<issue number>' will not close the issue, but help provide the reviewer more context.-->\n\n**What this PR does / why we need it**:\n\n**Special notes for your reviewer**:\n\n**If applicable**:\n- [ ] this PR contains documentation\n- [ ] this PR contains unit tests\n- [ ] this PR has been tested for backwards compatibility\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nThanks for choosing to contribute!\n\nThe following are a set of guidelines to follow when contributing to this project.\n\n## Have A Question?\n\nStart by filing an issue. The existing committers on this project work to reach\nconsensus around project direction and issue solutions within issue threads\n(when appropriate).\n\n## How to Contribute Code\n\n1. Fork the repo, develop and test your code changes.\n1. Submit a pull request.\n\nLastly, please follow the [pull request template](.github/PULL_REQUEST_TEMPLATE.md) when\nsubmitting a pull request!\n\n#### Documentation PRs\n\nDocumentation PRs will follow the same lifecycle as other PRs. They should also be labeled with the\n`docs` label. For documentation, special attention will be paid to spelling, grammar, and clarity\n(whereas those things don't matter *as* much for comments in code).\n\n<!-- ## Security Issues\n\nSecurity issues shouldn't be reported on this issue tracker. Instead, please email a report to\n[___@travelaudience.com](mailto:___@travelaudience.com). This will give\nus a chance to try to fix the issue before it is exploited in the wild.\n -->\n\n## Code Of Conduct\n\nThis project adheres to the travel audience [code of conduct](.github/CODE_OF_CONDUCT.md). By participating,\nyou are expected to uphold this code. Please report unacceptable behavior to\n[opensource@travelaudience.com](mailto:opensource@travelaudience.com).\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "MIT License\n\n© Copyright 2019 travel audience. All rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Some simple SQL extensions for Go\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/travelaudience/go-sx.svg)](https://pkg.go.dev/github.com/travelaudience/go-sx)\n[![CircleCI](https://circleci.com/gh/travelaudience/go-sx.svg?style=svg)](https://circleci.com/gh/travelaudience/go-sx)\n\n**go-sx** provides some extensions to the standard library `database/sql` package.  It is designed for those who wish to use the full power of SQL without a heavy abstraction layer.\n\nThis library is actively maintained.  Contributions are welcome.\n\n## Quickstart\n\nThis is what application code typically looks like.  The example here is for fetching multiple database rows into a\nslice of structs.  Note that the order of exported struct fields needs to match the columns in the SQL result set.\n\n```go\ntype exam struct {\n    ID    int64\n    Name  string\n    Score float64\n}\n\ndb, err := sql.Open(...)\nif err != nil {\n    ...\n}\n\nvar scores []exam\nerr = sx.Do(db, func(tx *sx.Tx) {\n    tx.MustQuery(\"SELECT id, name, score FROM exam_scores ORDER BY name\").Each(func(r *sx.Rows) {\n        var e exam\n        r.MustScans(&e)\n        scores = append(scores, e)\n    })\n})\nif err != nil {\n    ...\n}\n```\n\nFor more in-depth details, please keep reading!\n\n## Goals\n\nThe primary goal of **go-sx** is to eliminate boilerplate code.  Specifically, **go-sx** attempts to address the following pain points:\n\n1. Transactions are clumsy.  It would be nice to have a simple function to run a callback in a transaction.\n2. Error handling is clumsy.  It would be nice to have errors within a transaction automatically exit the transaction and trigger a rollback.  (This is nearly always what we want to do.)\n3. Scanning multiple columns is clumsy.  It would be nice to have a simple way to scan into multiple struct fields at once.\n4. Constructing queries is clumsy, especially when there are a lot of columns.\n5. Iterating over result sets is clumsy.\n\n## Non-goals\n\nThese are considered to be out of scope:\n\n1. Be an ORM.\n2. Write your queries for you.\n3. Suggest that we need 1:1 relationship between struct types and tables.\n4. Maintain database schemas.\n5. Abstract away differences between SQL dialects.\n6. Automatic type-manipulation.\n7. Magic.\n\n## Pain point #1:  Transactions are clumsy.\n\n**go-sx** provides a function `Do` to run a transaction in a callback, automatically committing on success or rolling back on failure.\n\nHere is some simple code to run two queries in a transaction.  The second query returns two values, which are read into variables `x` and `y`.\n\n```go\ntx, err := db.Begin()\nif err != nil {\n    return err\n}\nif _, err := tx.Exec(query0); err != nil {\n    tx.Rollback()\n    return err\n}\nif err := tx.QueryRow(query1).Scan(&x, &y); err != nil {\n    tx.Rollback()\n    return err\n}\nif err := tx.Commit(); err != nil {\n    return err\n}\n```\n\nUsing the `Do` function, we put the business logic into a callback function and have **go-sx** take care of the transaction logic.\n\nThe `sx.Tx` object provided to the callback is the `sql.Tx` transaction object, extended with a few methods.  If we call `tx.Fail()`, then the transaction is immediately aborted and rolled back.\n\n```go\nerr := sx.Do(db, func (tx *sx.Tx) {\n    if _, err := tx.Exec(query0); err != nil {\n        tx.Fail(err)\n    }\n    if err := tx.QueryRow(query1).Scan(&x, &y); err != nil {\n        tx.Fail(err)\n    }\n})\n```\n\nUnder the hood, `tx.Fail()` generates a panic which is recovered by `Do`.\n\n## Pain point #2: Error handling is clumsy.\n\n**go-sx** provides a collection of `Must***` methods which may be used inside of the callback to `Do`.  Any error encountered while in a `Must***` method causes the transaction to be aborted and rolled back.\n\nHere is the code above, rewritten to use `Do`'s error handling.  It's simple and readable.\n\n```go\nerr := sx.Do(db, func (tx *sx.Tx) {\n    tx.MustExec(query0)\n    tx.MustQueryRow(query1).MustScan(&x, &y)\n})\n```\n\n## Pain point #3:  Scanning multiple columns is clumsy.\n\n**go-sx** provides an `Addrs` function, which takes a struct and returns a slice of pointers to the elements.  So instead of:\n\n```go\nrow.Scan(&a.Width, &a.Height, &a.Depth)\n```\n\nWe can write:\n\n```go\nrow.Scan(sx.Addrs(&a)...)\n```\n\nOr better yet, let **go-sx** handle the errors:\n\n```go\nrow.MustScan(sx.Addrs(&a)...)\n```\n\nThis is such a common pattern that we provide a shortcut to do this all in one step:\n\n```go\nrow.MustScans(&a)\n```\n\n## Pain point #4:  Constructing queries is clumsy.\n\nWe would like **go-sx** to be able to construct some common queries for us.  To this end, we define a simple way to match struct fields with database columns, and then provide some helper functions that use this matching to construct queries.\n\nBy default, all exported struct fields match database columns whose name is the the field name snake_cased.  The default can be overridden by explicitly tagging fields, much like what is done with the standard json encoder.  Note that we don't care about the name of the table at this point.\n\nHere is a struct that can be used to scan columns `violin`, `viola`, `cello` and `contrabass`.\n\n```go\ntype orchestra struct {\n    Violin string\n    Viola  string\n    Cello  string\n    Bass   string `sx:\"contrabass\"`\n}\n```\n\nWe can use the helper function `SelectQuery` to construct a simple query.  Then we can add the WHERE clause that we need and scan the result set into our struct.\n\n```go\nvar spo orchestra\n\nwantID := 123\nquery := sx.SelectQuery(\"symphony\", &spo) + \" WHERE id=?\"  // SELECT violin,viola,cello,contrabass FROM symphony WHERE id=?\ntx.MustQueryRow(query, wantID).MustScans(&spo)\n```\n\nNote that a struct need not follow the database schema exactly.  It's entirely possible to have various structs mapped to different columns of the same table, or even one struct that maps to a query on joined tables.  On the other hand, it's essential that the columns in the query match the fields of the struct, and **go-sx** guarantees this, as we'll see below.\n\nIn some cases it's useful to have a struct that is used for both selects and inserts, with some of the fields being used just for selects.  This can be accomplished with the \"readonly\" tag.\n\n```go\ntype orchestra1 struct {\n    Violin string `sx:\",readonly\"`\n    Viola  string\n    Cello  string\n    Bass   string `sx:\"contrabass\"`\n}\n```\n\nIt's also useful in some cases to have a struct field that is ignored by **go-sx**.  This can be accomplished with the \"-\" tag.\n\n```go\ntype orchestra2 struct {\n    Violin string `sx:\",readonly\"`\n    Viola  string `sx:\"-\"`\n    Cello  string\n    Bass   string `sx:\"contrabass\"`\n}\n```\n\nWe can construct insert queries in a similar manner.  Violin is read-only and Viola is ignored, so we only need to provide values for Cello and Bass.  (If you need Postgres-style `$n` placeholders, see `sx.SetNumberedPlaceholders()`.)\n\n```go\nspo := orchestra2{Cello: \"Strad\", Bass: \"Cecilio\"}\n\nquery := sx.InsertQuery(\"symphony\", &spo)  // INSERT INTO symphony (cello,contrabass) VALUES (?,?)\ntx.MustExec(query, sx.Values(&spo)...)\n```\n\nWe can contruct update queries this way too, and there is also an option to skip fields whose values are the zero values.  (The update structs support pointer fields, making this skip option rather useful.)\n\n```go\nspoChanges := orchestra2{Bass: \"Strad\"}\n\nwantID := 123\nquery, values := sx.UpdateQuery(\"symphony\", &spoChanges) + \" WHERE id=?\"  // UPDATE symphony SET contrabass=? WHERE id=?\ntx.MustExec(query, append(values, wantID)...)\n```\n\nIt is entirely possible to construct all of these queries by hand, and you're all welcome to do so.  Using the query generators, however, ensures that the fields match correctly, something that is particularly useful with a large number of columns.\n\n## Pain point #5:  Iterating over result sets is clumsy.\n\n**go-sx** provides an iterator called `Each` which runs a callback function on each row of a result set.  Using the iterator simplifies this code:\n\n```go\nvar orchestras []orchestra\n\nquery := \"SELECT violin,viola,cello,contrabass FROM symphony ORDER BY viola\"  // Or we could use sx.SelectQuery()\nrows := tx.MustQuery(query)\ndefer rows.Close()\nfor rows.Next() {\n    var o orchestra\n    rows.MustScans(&o)\n    orchestras = append(orchestras, o)\n}\nif err := rows.Err(); err != nil {\n    tx.Fail(err)\n}\n```\n\nTo this:\n\n```go\nvar orchestras []orchestra\n\nquery := \"SELECT violin,viola,cello,contrabass FROM symphony ORDER BY viola\"\ntx.MustQuery(query).Each(func (r *sx.Rows) {\n    var o orchestra\n    r.MustScans(&o)\n    orchestras = append(orchestras, o)\n})\n```\n\n## Contributing\n\nContributions are welcome! Read the [Contributing Guide](CONTRIBUTING.md) for more information.\n\n## Licensing\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE.txt) file for details\n"
  },
  {
    "path": "doc.go",
    "content": "// Package sx provides some simple extensions to the database/sql package to reduce the amount of boilerplate code.\n//\n// Transactions and error handling\n//\n// Package sx provides a function called Do, which runs a callback function inside a transaction.  The callback\n// function is provided with a Tx object, which is an sql.Tx object that has been extended with some Must*** methods.\n// When a Must*** method encounters an error, it panics, and the panic is caught by Do and returned to the caller\n// as an error value.\n//\n// Do automatically commits or rolls back the transaction based on whether or not the callback function completed\n// successfuly.\n//\n// Query helpers and struct matching\n//\n// Package sx provides functions to generate frequently-used queries, based on a simple matching between struct\n// fields and database columns.\n//\n// By default, every field in a struct corresponds to the database column whose name is the snake-cased version of\n// the field name, i.e. the field HelloWorld corresponds to the \"hello_world\" column.  Acronyms are treated as words,\n// so HelloRPCWorld becomes \"hello_rpc_world\".\n//\n// The column name can also be specified explicitly by tagging the field with the desired name, and fields can be\n// excluded altogether by tagging with \"-\".\n//\n// Fields that should be used for scanning but exluded for inserts and updates are additionally tagged \"readonly\".\n//\n// Examples:\n//\n//     // Field is called \"field\" in the database.\n//     Field int\n//\n//     // Field is called \"hage\" in the database.\n//     Field int `sx:\"hage\"`\n//\n//     // Field is called \"hage\" in the database and should be skipped for inserts and updates.\n//     Field int `sx:\"hage,readonly\"`\n//\n//     // Field is called \"field\" in the database and should be skipped for inserts and updates.\n//     Field int `sx:\",readonly\"`\n//\n//     // Field should be ignored by sx.\n//     Field int `sx:\"-\"`\npackage sx\n"
  },
  {
    "path": "example_test.go",
    "content": "package sx_test\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\n\t_ \"github.com/mattn/go-sqlite3\"\n\tsx \"github.com/travelaudience/go-sx\"\n)\n\nfunc Example() {\n\tdb, err := sql.Open(\"sqlite3\", \":memory:\")\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\t_, err = db.Exec(\"CREATE TABLE numbers (foo integer, bar string)\")\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\t// This is the default, but other examples set numbered placeholders to true, so\n\t// we need to make sure here that it's false.  In practice, this would only be set\n\t// during initialization, and then only when $n-style placeholders are needed.\n\tsx.SetNumberedPlaceholders(false)\n\n\ttype abc struct {\n\t\tFoo int32\n\t\tBar string\n\t}\n\tvar data = []abc{\n\t\t{Foo: 1, Bar: \"one\"},\n\t\t{Foo: 2, Bar: \"two\"},\n\t\t{Foo: 3, Bar: \"three\"},\n\t}\n\n\t// Use Do to run a transaction.\n\tif err = sx.Do(db, func(tx *sx.Tx) {\n\t\t// Use MustPrepare with Do to insert rows into the table.\n\t\tquery := sx.InsertQuery(\"numbers\", &abc{})\n\t\ttx.MustPrepare(query).Do(func(s *sx.Stmt) {\n\t\t\tfor _, x := range data {\n\t\t\t\ts.MustExec(sx.Values(&x)...)\n\t\t\t}\n\t\t})\n\t}); err != nil {\n\t\t// Any database-level error will be caught and printed here.\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\tvar dataRead []abc\n\tif err = sx.Do(db, func(tx *sx.Tx) {\n\t\t// Use MustQuery with Each to read the rows back in alphabetical order.\n\t\tquery := sx.SelectQuery(\"numbers\", &abc{}) + \" ORDER BY bar\"\n\t\ttx.MustQuery(query).Each(func(r *sx.Rows) {\n\t\t\tvar x abc\n\t\t\tr.MustScans(&x)\n\t\t\tdataRead = append(dataRead, x)\n\t\t})\n\t}); err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\tfmt.Println(dataRead)\n\t// Output:\n\t// [{1 one} {3 three} {2 two}]\n}\n\nfunc ExampleSelectQuery() {\n\ttype abc struct {\n\t\tField1   int64\n\t\tFieldTwo string\n\t\tField3   bool `sx:\"gigo\"`\n\t}\n\tquery := sx.SelectQuery(\"sometable\", &abc{})\n\tfmt.Println(query)\n\t// Output:\n\t// SELECT field1,field_two,gigo FROM sometable\n}\n\nfunc ExampleSelectAliasQuery() {\n\ttype abc struct {\n\t\tFoo, Bar string\n\t}\n\tquery := sx.SelectAliasQuery(\"sometable\", \"s\", &abc{})\n\tfmt.Println(query)\n\t// Output:\n\t// SELECT s.foo,s.bar FROM sometable s\n}\n\nfunc ExampleWhere() {\n\tconditions := []string{\n\t\t\"ordered\",\n\t\t\"NOT sent\",\n\t}\n\tquery := \"SELECT * FROM sometable\" + sx.Where(conditions...)\n\tfmt.Println(query)\n\t// Output:\n\t// SELECT * FROM sometable WHERE (ordered) AND (NOT sent)\n}\n\nfunc ExampleLimitOffset() {\n\tquery := \"SELECT * FROM sometable\" + sx.LimitOffset(100, 0)\n\tfmt.Println(query)\n\t// Output:\n\t// SELECT * FROM sometable LIMIT 100\n}\n\nfunc ExampleInsertQuery() {\n\tsx.SetNumberedPlaceholders(true)\n\ttype abc struct {\n\t\tFoo, Bar string\n\t\tBaz      int64 `sx:\",readonly\"`\n\t}\n\tquery := sx.InsertQuery(\"sometable\", &abc{})\n\tfmt.Println(query)\n\t// Output:\n\t// INSERT INTO sometable (foo,bar) VALUES ($1,$2)\n}\n\nfunc ExampleUpdateQuery() {\n\tsx.SetNumberedPlaceholders(true)\n\ttype updateABC struct {\n\t\tFoo string  // cannot update to \"\"\n\t\tBar *string // can update to \"\"\n\t\tBaz int64   // cannot update to 0\n\t\tQux *int64  // can update to 0\n\t}\n\n\ts1, i1 := \"hello\", int64(0)\n\tx := updateABC{Bar: &s1, Baz: 42, Qux: &i1}\n\tquery, values := sx.UpdateQuery(\"sometable\", &x)\n\tquery += \" WHERE id=$1\"\n\tfmt.Println(query)\n\tfmt.Println(values)\n\n\tquery, values = sx.UpdateQuery(\"sometable\", &updateABC{})\n\tfmt.Println(query == \"\", len(values))\n\n\t// Output:\n\t// UPDATE sometable SET bar=$2,baz=$3,qux=$4 WHERE id=$1\n\t// [hello 42 0]\n\t// true 0\n}\n\nfunc ExampleUpdateAllQuery() {\n\tsx.SetNumberedPlaceholders(true)\n\ttype abc struct {\n\t\tFoo, Bar string\n\t\tBaz      int64 `sx:\",readonly\"`\n\t}\n\tquery := sx.UpdateAllQuery(\"sometable\", &abc{}) + \" WHERE id=$1\"\n\tfmt.Println(query)\n\t// Output:\n\t// UPDATE sometable SET foo=$2,bar=$3 WHERE id=$1\n}\n\nfunc ExampleUpdateFieldsQuery() {\n\tsx.SetNumberedPlaceholders(true)\n\ttype abc struct {\n\t\tFoo, Bar string\n\t\tBaz      int64\n\t}\n\tx := abc{Foo: \"hello\", Bar: \"Goodbye\", Baz: 42}\n\tquery, values := sx.UpdateFieldsQuery(\"sometable\", &x, \"Bar\", \"Baz\")\n\tquery += \" WHERE id=$1\"\n\tfmt.Println(query)\n\tfmt.Println(values)\n\t// Output:\n\t// UPDATE sometable SET bar=$2,baz=$3 WHERE id=$1\n\t// [Goodbye 42]\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/travelaudience/go-sx\n\ngo 1.23\n\nrequire (\n\tgithub.com/DATA-DOG/go-sqlmock v1.5.2\n\tgithub.com/mattn/go-sqlite3 v1.14.24\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=\ngithub.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=\ngithub.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=\ngithub.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=\ngithub.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=\n"
  },
  {
    "path": "helpers.go",
    "content": "package sx\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// SelectQuery returns a query string of the form\n//\n//\tSELECT <columns> FROM <table>\n//\n// where <columns> is the list of columns defined by the struct pointed at by datatype, and <table> is the table name\n// given.\nfunc SelectQuery(table string, datatype interface{}) string {\n\tbob := strings.Builder{}\n\tbob.WriteString(\"SELECT\")\n\tvar sep byte = ' '\n\tfor _, c := range matchingOf(datatype).columns {\n\t\tbob.WriteByte(sep)\n\t\tbob.WriteString(c.name)\n\t\tsep = ','\n\t}\n\tbob.WriteString(\" FROM \")\n\tbob.WriteString(table)\n\treturn bob.String()\n}\n\n// SelectAliasQuery returns a query string like that of SelectQuery except that a table alias is included, e.g.\n//\n//\tSELECT <alias>.<col0>, <alias>.<col1>, ..., <alias>.<coln> FROM <table> <alias>\nfunc SelectAliasQuery(table, alias string, datatype interface{}) string {\n\tbob := strings.Builder{}\n\tbob.WriteString(\"SELECT\")\n\tvar sep byte = ' '\n\tfor _, c := range matchingOf(datatype).columns {\n\t\tbob.WriteByte(sep)\n\t\tbob.WriteString(alias)\n\t\tbob.WriteByte('.')\n\t\tbob.WriteString(c.name)\n\t\tsep = ','\n\t}\n\tbob.WriteString(\" FROM \")\n\tbob.WriteString(table)\n\tbob.WriteByte(' ')\n\tbob.WriteString(alias)\n\treturn bob.String()\n}\n\n// Where returns a string of the form\n//\n//\tWHERE (<condition>) AND (<condition>) ...\n//\n// with a leading space.\n//\n// If no conditions are given, then Where returns the empty string.\nfunc Where(conditions ...string) string {\n\tif len(conditions) == 0 {\n\t\treturn \"\"\n\t}\n\treturn \" WHERE (\" + strings.Join(conditions, \") AND (\") + \")\"\n}\n\n// LimitOffset returns a string of the form\n//\n//\tLIMIT <limit> OFFSET <offset>\n//\n// with a leading space.\n//\n// If either limit or offset are zero, then that part of the string is omitted.  If both limit and offset are zero,\n// then LimitOffset returns the empty string.\nfunc LimitOffset(limit, offset int64) string {\n\tx := \"\"\n\tif limit != 0 {\n\t\tx = \" LIMIT \" + strconv.FormatInt(limit, 10)\n\t}\n\tif offset != 0 {\n\t\tx += \" OFFSET \" + strconv.FormatInt(offset, 10)\n\t}\n\treturn x\n}\n\n// InsertQuery returns a query string of the form\n//\n//\tINSERT INTO <table> (<columns>) VALUES (?,?,...)\n//\tINSERT INTO <table> (<columns>) VALUES ($1,$2,...)  (numbered placeholders)\n//\n// where <table> is the table name given, and <columns> is the list of the columns defined by the struct pointed at by\n// datatype.  Struct fields tagged \"readonly\" are skipped.\n//\n// Panics if all fields are tagged \"readonly\".\nfunc InsertQuery(table string, datatype interface{}) string {\n\tcolumns := matchingOf(datatype).columns\n\tbob := strings.Builder{}\n\tbob.WriteString(\"INSERT INTO \")\n\tbob.WriteString(table)\n\tbob.WriteByte(' ')\n\tvar sep byte = '('\n\tvar n int\n\tfor _, c := range columns {\n\t\tif !c.readonly {\n\t\t\tbob.WriteByte(sep)\n\t\t\tbob.WriteString(c.name)\n\t\t\tsep = ','\n\t\t\tn++\n\t\t}\n\t}\n\tif n == 0 {\n\t\tpanic(\"sx: struct \" + matchingOf(datatype).reflectType.Name() + \" has no writeable fields\")\n\t}\n\tbob.WriteString(\") VALUES \")\n\tsep = '('\n\tfor p := Placeholder(0); p < Placeholder(n); {\n\t\tbob.WriteByte(sep)\n\t\tbob.WriteString(p.Next())\n\t\tsep = ','\n\t}\n\tbob.WriteByte(')')\n\treturn bob.String()\n}\n\n// UpdateQuery returns a query string and a list of values from the struct pointed at by data.  This is the prefferred\n// way to do updates, as it allows pointer fields in the struct and automatically skips zero values.\n//\n// The query string of the form\n//\n//\tUPDATE <table> SET <column>=?,<column>=?,...\n//\tUPDATE <table> SET <column>=$2,<column>=$3,...  (numbered placeholders)\n//\n// where <table> is the table name given, and each <column> is a column name defined by the struct pointed at by data.\n//\n// Note:\n//   - placeholder could be passed as an optional parameter ( to control numbered placeholders )\n//   - if not, numbering starts at $2 to allow $1 to be used in the WHERE clause ( ie. WHERE id = $1 ).\n//\n// The list of values contains values from the struct to match the placeholders.  For pointer fields, the values\n// pointed at are used.\n//\n// UpdateQuery takes all the writeable fields (not tagged \"readonly\") from the struct, looks up their values, and if\n// it finds a zero value, the field is skipped.  This allows the caller to set only those values that need updating.\n// If it is necessary to update a field to a zero value, then a pointer field should be used.  A pointer to a zero\n// value will force an update, and a nil pointer will be skipped.\n//\n// The struct used for UpdateQuery will normally be a different struct from that used for select or insert on the\n// same table.  This is okay.\n//\n// If there are no applicable fields, Update returns (\"\", nil).\nfunc UpdateQuery(table string, data interface{}, ph ...*Placeholder) (string, []interface{}) {\n\tm := matchingOf(data)\n\tinstance := reflect.ValueOf(data).Elem()\n\n\tcolumns := make([]string, 0)\n\tvalues := make([]interface{}, 0)\n\n\t// check for optional placeholder provided in the parameters\n\tvar p *Placeholder\n\tif len(ph) > 0 {\n\t\tp = ph[0]\n\t} else {\n\t\t// if not, use default one that starts from 2\n\t\tvar defaultPh Placeholder = 1\n\t\tp = &defaultPh\n\t}\n\n\tfor _, c := range m.columns {\n\t\tif !c.readonly {\n\t\t\tif val := instance.Field(c.index); !val.IsZero() {\n\t\t\t\tcolumns = append(columns, c.name+\"=\"+p.Next())\n\t\t\t\tif val.Kind() == reflect.Ptr {\n\t\t\t\t\tval = val.Elem()\n\t\t\t\t}\n\t\t\t\tvalues = append(values, val.Interface())\n\t\t\t}\n\t\t}\n\t}\n\tif len(columns) == 0 {\n\t\treturn \"\", nil\n\t}\n\n\treturn \"UPDATE \" + table + \" SET \" + strings.Join(columns, \",\"), values\n}\n\n// UpdateAllQuery returns a query string of the form\n//\n//\tUPDATE <table> SET <column>=?,<column>=?,...\n//\tUPDATE <table> SET <column>=$2,<column>=$3,...  (numbered placeholders)\n//\n// where <table> is the table name given, and each <column> is a column name defined by the struct pointed at by\n// data.  All writeable fields (those not tagged \"readonly\") are included.  Fields are in the order of the struct.\n//\n// With numbered placeholders, numbering starts at $2.  This allows $1 to be used in the WHERE clause.\n//\n// Use with the Values function to write to all writeable feilds.\nfunc UpdateAllQuery(table string, data interface{}) string {\n\tm := matchingOf(data)\n\tcolumns := make([]string, 0)\n\tvar p Placeholder = 1 // start from 2\n\n\tfor _, c := range m.columns {\n\t\tif !c.readonly {\n\t\t\tcolumns = append(columns, c.name+\"=\"+p.Next())\n\t\t}\n\t}\n\n\treturn \"UPDATE \" + table + \" SET \" + strings.Join(columns, \",\")\n}\n\n// UpdateFieldsQuery returns a query string and a list of values for the specified fields of the struct pointed at by data.\n//\n// The query string is of the form\n//\n//\tUPDATE <table> SET <column>=?,<column>=?,...\n//\tUPDATE <table> SET <column>=$2,<column>=$3,...  (numbered placeholders)\n//\n// where <table> is the table name given, and each <column> is a column name defined by the struct pointed at by data.\n//\n// The list of values contains values from the struct to match the placeholders.  The order matches the the order of\n// fields provided by the caller.\n//\n// With numbered placeholders, numbering starts at $2.  This allows $1 to be used in the WHERE clause.\n//\n// UpdateFieldsQuery panics if no field names are provided or if any of the requested fields do not exist.  If it is\n// necessary to validate field names, use ColumnOf.\nfunc UpdateFieldsQuery(table string, data interface{}, fields ...string) (string, []interface{}) {\n\tm := matchingOf(data)\n\tinstance := reflect.ValueOf(data).Elem()\n\n\tcolumns := make([]string, 0)\n\tvalues := make([]interface{}, 0)\n\tvar p Placeholder = 1 // start from 2\n\n\tif len(fields) == 0 {\n\t\tpanic(\"UpdateFieldsQuery requires at least one field\")\n\t}\n\tfor _, field := range fields {\n\t\tif c, ok := m.columnMap[field]; ok {\n\t\t\tcolumns = append(columns, c.name+\"=\"+p.Next())\n\t\t\tvalues = append(values, instance.Field(c.index).Interface())\n\t\t} else {\n\t\t\tpanic(\"struct \" + m.reflectType.Name() + \" has no usable field \" + field)\n\t\t}\n\t}\n\n\treturn \"UPDATE \" + table + \" SET \" + strings.Join(columns, \",\"), values\n}\n\n// Addrs returns a slice of pointers to the fields of the struct pointed at by dest.  Use for scanning rows from a\n// SELECT query.\n//\n// Panics if dest does not point at a struct.\nfunc Addrs(dest interface{}) []interface{} {\n\tm := matchingOf(dest)\n\tval := reflect.ValueOf(dest).Elem()\n\taddrs := make([]interface{}, 0, len(m.columns))\n\tfor _, c := range m.columns {\n\t\taddrs = append(addrs, val.Field(c.index).Addr().Interface())\n\t}\n\treturn addrs\n}\n\n// Values returns a slice of values from the struct pointed at by data, excluding those from fields tagged \"readonly\".\n// Use for providing values to an INSERT query.\n//\n// Panics if data does not point at a struct.\nfunc Values(data interface{}) []interface{} {\n\tm := matchingOf(data)\n\tval := reflect.ValueOf(data).Elem()\n\tvalues := make([]interface{}, 0, len(m.columns))\n\tfor _, c := range m.columns {\n\t\tif !c.readonly {\n\t\t\tvalues = append(values, val.Field(c.index).Interface())\n\t\t}\n\t}\n\treturn values\n}\n\n// ValueOf returns the value of the specified field of the struct pointed at by data.  Panics if data does not\n// point at a struct, or if the requested field doesn't exist.\nfunc ValueOf(data interface{}, field string) interface{} {\n\t// This step verifies data and field and might panic.\n\tc := matchingOf(data).columnOf(field)\n\t// If there is a panic, then the reflection here will not be attempted.\n\treturn reflect.ValueOf(data).Elem().Field(c.index).Interface()\n}\n\n// Columns returns the names of the database columns that correspond to the fields in the struct pointed at by\n// datatype.  The order of returned fields matches the order of the struct.\nfunc Columns(datatype interface{}) []string {\n\treturn matchingOf(datatype).columnList()\n}\n\n// ColumnsWriteable returns the names of the database columns that correspond to the fields in the struct pointed at\n// by datatype, excluding those tagged \"readonly\".  The order of returned fields matches the order of the struct.\nfunc ColumnsWriteable(datatype interface{}) []string {\n\treturn matchingOf(datatype).columnWriteableList()\n}\n\n// ColumnOf returns the name of the database column that corresponds to the specified field of the struct pointed\n// at by datatype.\n//\n// ColumnOf returns an error if the provided field name is missing from the struct.\nfunc ColumnOf(datatype interface{}, field string) (string, error) {\n\tm := matchingOf(datatype)\n\tif c, ok := m.columnMap[field]; ok {\n\t\treturn c.name, nil\n\t}\n\treturn \"\", errors.New(\"struct \" + m.reflectType.Name() + \" has no usable field \" + field)\n}\n"
  },
  {
    "path": "helpers_test.go",
    "content": "package sx_test\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\tsx \"github.com/travelaudience/go-sx\"\n)\n\n// Test structs\n\ntype menagerie0 struct {\n\tPlatypus   string\n\tRhinoceros float64\n}\n\ntype menagerie1 struct {\n\tChimpanzee int64  `sx:\"human\"`\n\tFlamingo   string `sx:\",readonly\"`\n\tWarthog    string `sx:\"-\"`\n}\n\nfunc TestSelectInsertUpdateAll(t *testing.T) {\n\n\tvar testCases = []struct {\n\t\tname                 string\n\t\ttable                string\n\t\tdatatype             interface{}\n\t\tnumberedPlaceholders bool\n\t\twantSelect           string\n\t\twantInsert           string\n\t\twantUpdate           string\n\t}{\n\t\t{\n\t\t\tname:                 \"menagerie0\",\n\t\t\ttable:                \"zoo\",\n\t\t\tdatatype:             &menagerie0{},\n\t\t\tnumberedPlaceholders: false,\n\t\t\twantSelect:           \"SELECT platypus,rhinoceros FROM zoo\",\n\t\t\twantInsert:           \"INSERT INTO zoo (platypus,rhinoceros) VALUES (?,?)\",\n\t\t\twantUpdate:           \"UPDATE zoo SET platypus=?,rhinoceros=?\",\n\t\t},\n\t\t{\n\t\t\tname:                 \"menagerie0 numbered\",\n\t\t\ttable:                \"zoo\",\n\t\t\tdatatype:             &menagerie0{},\n\t\t\tnumberedPlaceholders: true,\n\t\t\twantSelect:           \"SELECT platypus,rhinoceros FROM zoo\",\n\t\t\twantInsert:           \"INSERT INTO zoo (platypus,rhinoceros) VALUES ($1,$2)\",\n\t\t\twantUpdate:           \"UPDATE zoo SET platypus=$2,rhinoceros=$3\",\n\t\t},\n\t\t{\n\t\t\tname:                 \"menagerie1\",\n\t\t\ttable:                \"jungle\",\n\t\t\tdatatype:             &menagerie1{},\n\t\t\tnumberedPlaceholders: false,\n\t\t\twantSelect:           \"SELECT human,flamingo FROM jungle\",\n\t\t\twantInsert:           \"INSERT INTO jungle (human) VALUES (?)\",\n\t\t\twantUpdate:           \"UPDATE jungle SET human=?\",\n\t\t},\n\t\t{\n\t\t\tname:                 \"menagerie1 numbered\",\n\t\t\ttable:                \"jungle\",\n\t\t\tdatatype:             &menagerie1{},\n\t\t\tnumberedPlaceholders: true,\n\t\t\twantSelect:           \"SELECT human,flamingo FROM jungle\",\n\t\t\twantInsert:           \"INSERT INTO jungle (human) VALUES ($1)\",\n\t\t\twantUpdate:           \"UPDATE jungle SET human=$2\",\n\t\t},\n\t}\n\n\tfor _, c := range testCases {\n\t\tsx.SetNumberedPlaceholders(c.numberedPlaceholders)\n\t\tif a, b := c.wantSelect, sx.SelectQuery(c.table, c.datatype); a != b {\n\t\t\tt.Errorf(\"case %s select: expected \\\"%s\\\", got \\\"%s\\\"\", c.name, a, b)\n\t\t}\n\t\tif a, b := c.wantInsert, sx.InsertQuery(c.table, c.datatype); a != b {\n\t\t\tt.Errorf(\"case %s insert: expected \\\"%s\\\", got \\\"%s\\\"\", c.name, a, b)\n\t\t}\n\t\tif a, b := c.wantUpdate, sx.UpdateAllQuery(c.table, c.datatype); a != b {\n\t\t\tt.Errorf(\"case %s update all: expected \\\"%s\\\", got \\\"%s\\\"\", c.name, a, b)\n\t\t}\n\t}\n}\n\nfunc TestSelectAlias(t *testing.T) {\n\n\tvar testCases = []struct {\n\t\tname       string\n\t\ttable      string\n\t\talias      string\n\t\tdatatype   interface{}\n\t\twantSelect string\n\t}{\n\t\t{\n\t\t\tname:       \"menagerie0\",\n\t\t\ttable:      \"zoo\",\n\t\t\talias:      \"home\",\n\t\t\tdatatype:   &menagerie0{},\n\t\t\twantSelect: \"SELECT home.platypus,home.rhinoceros FROM zoo home\",\n\t\t},\n\t\t{\n\t\t\tname:       \"menagerie1\",\n\t\t\ttable:      \"jungle\",\n\t\t\talias:      \"a\",\n\t\t\tdatatype:   &menagerie1{},\n\t\t\twantSelect: \"SELECT a.human,a.flamingo FROM jungle a\",\n\t\t},\n\t}\n\n\tfor _, c := range testCases {\n\t\tif a, b := c.wantSelect, sx.SelectAliasQuery(c.table, c.alias, c.datatype); a != b {\n\t\t\tt.Errorf(\"case %s select alias: expected \\\"%s\\\", got \\\"%s\\\"\", c.name, a, b)\n\t\t}\n\t}\n}\n\nfunc TestWhere(t *testing.T) {\n\n\tvar testCases = []struct {\n\t\tname       string\n\t\tconditions []string\n\t\twant       string\n\t}{\n\t\t{\n\t\t\tname: \"empty\",\n\t\t},\n\t\t{\n\t\t\tname:       \"one condition\",\n\t\t\tconditions: []string{\"a=5\"},\n\t\t\twant:       \" WHERE (a=5)\",\n\t\t},\n\t\t{\n\t\t\tname:       \"two conditions\",\n\t\t\tconditions: []string{\"a=5\", \"b=6\"},\n\t\t\twant:       \" WHERE (a=5) AND (b=6)\",\n\t\t},\n\t\t{\n\t\t\tname:       \"three conditions\",\n\t\t\tconditions: []string{\"a=5\", \"b=6\", \"c=7\"},\n\t\t\twant:       \" WHERE (a=5) AND (b=6) AND (c=7)\",\n\t\t},\n\t}\n\n\tfor _, c := range testCases {\n\t\tif a, b := c.want, sx.Where(c.conditions...); a != b {\n\t\t\tt.Errorf(\"case %s: expected \\\"%s\\\", got \\\"%s\\\"\", c.name, a, b)\n\t\t}\n\t}\n}\n\nfunc TestLimitOffset(t *testing.T) {\n\n\tvar testCases = []struct {\n\t\tname   string\n\t\tlimit  int64\n\t\toffset int64\n\t\twant   string\n\t}{\n\t\t{\n\t\t\tname: \"empty\",\n\t\t},\n\t\t{\n\t\t\tname:  \"limit only\",\n\t\t\tlimit: 100,\n\t\t\twant:  \" LIMIT 100\",\n\t\t},\n\t\t{\n\t\t\tname:   \"offset only\",\n\t\t\toffset: 200,\n\t\t\twant:   \" OFFSET 200\",\n\t\t},\n\t\t{\n\t\t\tname:   \"limit and offset\",\n\t\t\tlimit:  123,\n\t\t\toffset: 456,\n\t\t\twant:   \" LIMIT 123 OFFSET 456\",\n\t\t},\n\t\t{\n\t\t\tname:   \"negative numbers\", // no practical use for this, but the function should still process it\n\t\t\tlimit:  -3,\n\t\t\toffset: -999999,\n\t\t\twant:   \" LIMIT -3 OFFSET -999999\",\n\t\t},\n\t}\n\n\tfor _, c := range testCases {\n\t\tif a, b := c.want, sx.LimitOffset(c.limit, c.offset); a != b {\n\t\t\tt.Errorf(\"case %s: expected \\\"%s\\\", got \\\"%s\\\"\", c.name, a, b)\n\t\t}\n\t}\n}\n\nfunc TestInsertPanic(t *testing.T) {\n\t// InsertQuery should panic if all of a struct's fields are tagged readonly.\n\ttype ohNo struct {\n\t\t_ int `sx:\",readonly\"`\n\t\t_ int `sx:\",readonly\"`\n\t}\n\tconst wantPanic = \"sx: struct ohNo has no usable fields\"\n\n\tfunc() {\n\t\tdefer func() {\n\t\t\tr := recover()\n\t\t\tif r == nil {\n\t\t\t\tt.Errorf(\"expected a panic\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif s, ok := r.(string); ok {\n\t\t\t\tif s != wantPanic {\n\t\t\t\t\tt.Errorf(\"expected \\\"%s\\\", got \\\"%s\\\"\", wantPanic, s)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tpanic(r)\n\t\t}()\n\t\tsx.InsertQuery(\"zoo\", &ohNo{})\n\t}()\n}\n\nfunc TestUpdate(t *testing.T) {\n\n\ttype menagerie2 struct {\n\t\tCougar      string\n\t\tCheetah     *string `sx:\"cat\"`\n\t\tGrizzly     int32\n\t\tWilderbeast []int32\n\t\tAnt         bool `sx:\"-\"`\n\t\tBee         bool `sx:\",readonly\"`\n\t}\n\n\tvar (\n\t\tx = []int32{1, 2, 3}\n\t\ty = int32(5)\n\t\ts = \"abcde\"\n\t)\n\n\tvar testCases = []struct {\n\t\tname                 string\n\t\ttable                string\n\t\tdata                 interface{}\n\t\tnumberedPlaceholders bool\n\t\twantQuery            string\n\t\twantValues           []interface{}\n\t}{\n\t\t{\n\t\t\tname:                 \"empty\",\n\t\t\ttable:                \"africa\",\n\t\t\tdata:                 &menagerie2{},\n\t\t\tnumberedPlaceholders: false,\n\t\t\twantQuery:            \"\",\n\t\t\twantValues:           nil,\n\t\t},\n\t\t{\n\t\t\tname:                 \"ignore ant and bee\",\n\t\t\ttable:                \"asia\",\n\t\t\tdata:                 &menagerie2{Ant: true, Bee: true},\n\t\t\tnumberedPlaceholders: true,\n\t\t\twantQuery:            \"\",\n\t\t\twantValues:           nil,\n\t\t},\n\t\t{\n\t\t\tname:                 \"cougar and wilderbeast\",\n\t\t\ttable:                \"australia\",\n\t\t\tdata:                 &menagerie2{Cougar: \"abc\", Wilderbeast: x},\n\t\t\tnumberedPlaceholders: false,\n\t\t\twantQuery:            \"UPDATE australia SET cougar=?,wilderbeast=?\",\n\t\t\twantValues:           []interface{}{\"abc\", x},\n\t\t},\n\t\t{\n\t\t\tname:                 \"cougar and wilderbeast numbered\",\n\t\t\ttable:                \"australia\",\n\t\t\tdata:                 &menagerie2{Cougar: \"abc\", Wilderbeast: x},\n\t\t\tnumberedPlaceholders: true,\n\t\t\twantQuery:            \"UPDATE australia SET cougar=$2,wilderbeast=$3\",\n\t\t\twantValues:           []interface{}{\"abc\", x},\n\t\t},\n\t\t{\n\t\t\tname:                 \"cheetah and grizzly\",\n\t\t\ttable:                \"siberia\",\n\t\t\tdata:                 &menagerie2{Cheetah: &s, Grizzly: y},\n\t\t\tnumberedPlaceholders: false,\n\t\t\twantQuery:            \"UPDATE siberia SET cat=?,grizzly=?\",\n\t\t\twantValues:           []interface{}{s, y},\n\t\t},\n\t\t{\n\t\t\tname:                 \"cheetah and grizzly numbered\",\n\t\t\ttable:                \"siberia\",\n\t\t\tdata:                 &menagerie2{Cheetah: &s, Grizzly: y},\n\t\t\tnumberedPlaceholders: true,\n\t\t\twantQuery:            \"UPDATE siberia SET cat=$2,grizzly=$3\",\n\t\t\twantValues:           []interface{}{s, y},\n\t\t},\n\t\t{\n\t\t\tname:                 \"everyone except ant\",\n\t\t\ttable:                \"berlin\",\n\t\t\tdata:                 &menagerie2{Cougar: \"roar\", Cheetah: &s, Grizzly: y, Wilderbeast: x, Bee: true},\n\t\t\tnumberedPlaceholders: false,\n\t\t\twantQuery:            \"UPDATE berlin SET cougar=?,cat=?,grizzly=?,wilderbeast=?\",\n\t\t\twantValues:           []interface{}{\"roar\", s, y, x},\n\t\t},\n\t\t{\n\t\t\tname:                 \"everyone except bee\",\n\t\t\ttable:                \"berlin\",\n\t\t\tdata:                 &menagerie2{Cougar: \"roar\", Cheetah: &s, Grizzly: y, Wilderbeast: x, Ant: true},\n\t\t\tnumberedPlaceholders: true,\n\t\t\twantQuery:            \"UPDATE berlin SET cougar=$2,cat=$3,grizzly=$4,wilderbeast=$5\",\n\t\t\twantValues:           []interface{}{\"roar\", s, y, x},\n\t\t},\n\t}\n\n\tfor _, c := range testCases {\n\t\tsx.SetNumberedPlaceholders(c.numberedPlaceholders)\n\t\tquery, values := sx.UpdateQuery(c.table, c.data)\n\t\tif a, b := c.wantQuery, query; a != b {\n\t\t\tt.Errorf(\"case %s query: expected \\\"%s\\\", got \\\"%s\\\"\", c.name, a, b)\n\t\t}\n\t\tif a, b := c.wantValues, values; !reflect.DeepEqual(a, b) {\n\t\t\tt.Errorf(\"case %s values: expected %v, got %v\", c.name, a, b)\n\t\t}\n\t}\n}\n\nfunc TestUpdateFields(t *testing.T) {\n\n\tvar testCases = []struct {\n\t\tname                 string\n\t\ttable                string\n\t\tdata                 interface{}\n\t\tfields               []string\n\t\tnumberedPlaceholders bool\n\t\twantQuery            string\n\t\twantValues           []interface{}\n\t\twantPanic            string\n\t}{\n\t\t{\n\t\t\tname:      \"no fields\",\n\t\t\ttable:     \"irrelevant\",\n\t\t\tdata:      &menagerie0{},\n\t\t\twantPanic: \"UpdateFieldsQuery requires at least one field\",\n\t\t},\n\t\t{\n\t\t\tname:      \"invalid field\",\n\t\t\ttable:     \"irrelevant\",\n\t\t\tdata:      &menagerie0{},\n\t\t\tfields:    []string{\"Research\"},\n\t\t\twantPanic: \"struct menagerie0 has no usable field Research\",\n\t\t},\n\t\t{\n\t\t\tname:      \"ignored field\",\n\t\t\ttable:     \"irrelevant\",\n\t\t\tdata:      &menagerie1{},\n\t\t\tfields:    []string{\"Warthog\"},\n\t\t\twantPanic: \"struct menagerie1 has no usable field Warthog\",\n\t\t},\n\t\t{\n\t\t\tname:                 \"rhinoceros\",\n\t\t\ttable:                \"swamp\",\n\t\t\tdata:                 &menagerie0{Platypus: \"abc\", Rhinoceros: -5.0},\n\t\t\tfields:               []string{\"Rhinoceros\"},\n\t\t\tnumberedPlaceholders: false,\n\t\t\twantQuery:            \"UPDATE swamp SET rhinoceros=?\",\n\t\t\twantValues:           []interface{}{float64(-5.0)},\n\t\t},\n\t\t{\n\t\t\tname:                 \"rhinoceros numbered\",\n\t\t\ttable:                \"swamp\",\n\t\t\tdata:                 &menagerie0{Platypus: \"abc\", Rhinoceros: -10.0},\n\t\t\tfields:               []string{\"Rhinoceros\"},\n\t\t\tnumberedPlaceholders: true,\n\t\t\twantQuery:            \"UPDATE swamp SET rhinoceros=$2\",\n\t\t\twantValues:           []interface{}{float64(-10.0)},\n\t\t},\n\t\t{\n\t\t\tname:                 \"rhinoceros and platypus\",\n\t\t\ttable:                \"swamp\",\n\t\t\tdata:                 &menagerie0{Platypus: \"abc\", Rhinoceros: -5.0},\n\t\t\tfields:               []string{\"Rhinoceros\", \"Platypus\"},\n\t\t\tnumberedPlaceholders: false,\n\t\t\twantQuery:            \"UPDATE swamp SET rhinoceros=?,platypus=?\",\n\t\t\twantValues:           []interface{}{float64(-5.0), \"abc\"},\n\t\t},\n\t\t{\n\t\t\tname:                 \"rhinoceros and platypus numbered\",\n\t\t\ttable:                \"swamp\",\n\t\t\tdata:                 &menagerie0{Platypus: \"abc\", Rhinoceros: 10.0},\n\t\t\tfields:               []string{\"Rhinoceros\", \"Platypus\"},\n\t\t\tnumberedPlaceholders: true,\n\t\t\twantQuery:            \"UPDATE swamp SET rhinoceros=$2,platypus=$3\",\n\t\t\twantValues:           []interface{}{float64(10.0), \"abc\"},\n\t\t},\n\t\t{\n\t\t\tname:                 \"can update readonly field\", // TODO: should we allow this?\n\t\t\ttable:                \"forest\",\n\t\t\tdata:                 &menagerie1{Chimpanzee: 123, Flamingo: \"foo\"},\n\t\t\tfields:               []string{\"Chimpanzee\", \"Flamingo\"},\n\t\t\tnumberedPlaceholders: false,\n\t\t\twantQuery:            \"UPDATE forest SET human=?,flamingo=?\",\n\t\t\twantValues:           []interface{}{int64(123), \"foo\"},\n\t\t},\n\t\t{\n\t\t\tname:                 \"can update readonly field numbered\",\n\t\t\ttable:                \"forest\",\n\t\t\tdata:                 &menagerie1{Chimpanzee: 123, Flamingo: \"foo\"},\n\t\t\tfields:               []string{\"Chimpanzee\", \"Flamingo\"},\n\t\t\tnumberedPlaceholders: true,\n\t\t\twantQuery:            \"UPDATE forest SET human=$2,flamingo=$3\",\n\t\t\twantValues:           []interface{}{int64(123), \"foo\"},\n\t\t},\n\t}\n\n\ttype _ struct {\n\t\tChimpanzee int64  `sx:\"human\"`\n\t\tFlamingo   string `sx:\",readonly\"`\n\t\tWarthog    string `sx:\"-\"`\n\t}\n\n\tfor _, c := range testCases {\n\n\t\tvar (\n\t\t\tquery    string\n\t\t\tvalues   []interface{}\n\t\t\tgotPanic string\n\t\t)\n\n\t\tsx.SetNumberedPlaceholders(c.numberedPlaceholders)\n\n\t\tfunc() {\n\t\t\tgotPanic = \"\"\n\t\t\tdefer func() {\n\t\t\t\tr := recover()\n\t\t\t\tif r == nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif s, ok := r.(string); ok {\n\t\t\t\t\tgotPanic = s\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tpanic(r)\n\t\t\t}()\n\t\t\tquery, values = sx.UpdateFieldsQuery(c.table, c.data, c.fields...)\n\t\t}()\n\n\t\tif gotPanic != c.wantPanic {\n\t\t\tif c.wantPanic == \"\" {\n\t\t\t\tt.Errorf(\"case %s: unexpected panic %q\", c.name, gotPanic)\n\t\t\t} else if gotPanic == \"\" {\n\t\t\t\tt.Errorf(\"case %s: expected panic %q but got none\", c.name, c.wantPanic)\n\t\t\t} else {\n\t\t\t\tt.Errorf(\"case %s: expected panic %q, got %q\", c.name, c.wantPanic, gotPanic)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif a, b := c.wantQuery, query; a != b {\n\t\t\tt.Errorf(\"case %s query: expected \\\"%s\\\", got \\\"%s\\\"\", c.name, a, b)\n\t\t}\n\t\tif a, b := c.wantValues, values; !reflect.DeepEqual(a, b) {\n\t\t\tt.Errorf(\"case %s values: expected %v, got %v\", c.name, a, b)\n\t\t}\n\t}\n}\n\nfunc TestAddrsValues(t *testing.T) {\n\n\tvar (\n\t\tdata0 = menagerie0{Platypus: \"yes\", Rhinoceros: 1.0}\n\t\tdata1 = menagerie1{Chimpanzee: 64, Flamingo: \"maybe\", Warthog: \"no\"}\n\t)\n\n\tvar testCases = []struct {\n\t\tname       string\n\t\tdata       interface{}\n\t\twantAddrs  []interface{}\n\t\twantValues []interface{}\n\t}{\n\t\t{\n\t\t\tname:       \"menagerie0\",\n\t\t\tdata:       &data0,\n\t\t\twantAddrs:  []interface{}{&data0.Platypus, &data0.Rhinoceros},\n\t\t\twantValues: []interface{}{\"yes\", float64(1.0)},\n\t\t},\n\t\t{\n\t\t\tname:       \"menagerie1\",\n\t\t\tdata:       &data1,\n\t\t\twantAddrs:  []interface{}{&data1.Chimpanzee, &data1.Flamingo},\n\t\t\twantValues: []interface{}{int64(64)},\n\t\t},\n\t}\n\n\t// What's returned from Addrs is a slice of pointers, and we need to test that these are the exact pointers\n\t// that we want.  We can get a false true from DeepEqual, since DeepEqual considers two distinct pointers whose\n\t// pointed-at values are the same to be equal.\n\tshallowEqual := func(a, b []interface{}) bool {\n\t\tif len(a) != len(b) {\n\t\t\treturn false\n\t\t}\n\t\tfor i := range a {\n\t\t\tif a[i] != b[i] {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\tfor _, c := range testCases {\n\t\tif a, b := c.wantAddrs, sx.Addrs(c.data); !shallowEqual(a, b) {\n\t\t\tt.Errorf(\"case %s addrs: expected %v, got %v\", c.name, a, b)\n\t\t}\n\t\tif a, b := c.wantValues, sx.Values(c.data); !reflect.DeepEqual(a, b) {\n\t\t\tt.Errorf(\"case %s values: expected %v, got %v\", c.name, a, b)\n\t\t}\n\t}\n}\n\nfunc TestValueOf(t *testing.T) {\n\n\tvar testCases = []struct {\n\t\tname      string\n\t\tdata      interface{}\n\t\tfield     string\n\t\twantValue interface{}\n\t}{\n\t\t{\n\t\t\tname:      \"platypus\",\n\t\t\tdata:      &menagerie0{Platypus: \"pus\", Rhinoceros: 2.0},\n\t\t\tfield:     \"Platypus\",\n\t\t\twantValue: \"pus\",\n\t\t},\n\t\t{\n\t\t\tname:      \"rhinoceros\",\n\t\t\tdata:      &menagerie0{Platypus: \"pus\", Rhinoceros: 2.0},\n\t\t\tfield:     \"Rhinoceros\",\n\t\t\twantValue: float64(2.0),\n\t\t},\n\t\t{\n\t\t\tname:      \"chimpanzee\",\n\t\t\tdata:      &menagerie1{Chimpanzee: 641, Flamingo: \"goth\", Warthog: \"hog\"},\n\t\t\tfield:     \"Chimpanzee\",\n\t\t\twantValue: int64(641),\n\t\t},\n\t\t{\n\t\t\tname:      \"flamingo\",\n\t\t\tdata:      &menagerie1{Chimpanzee: 641, Flamingo: \"goth\", Warthog: \"hog\"},\n\t\t\tfield:     \"Flamingo\",\n\t\t\twantValue: \"goth\",\n\t\t},\n\t}\n\n\tfor _, c := range testCases {\n\t\tif a, b := c.wantValue, sx.ValueOf(c.data, c.field); a != b {\n\t\t\tt.Errorf(\"case %s: expected %v, got %v\", c.name, a, b)\n\t\t}\n\t}\n}\n\nfunc TestColumnsColumnsWriteable(t *testing.T) {\n\n\tvar testCases = []struct {\n\t\tname                 string\n\t\tdatatype             interface{}\n\t\twantColumns          []string\n\t\twantColumnsWriteable []string\n\t}{\n\t\t{\n\t\t\tname:                 \"menagerie0\",\n\t\t\tdatatype:             &menagerie0{},\n\t\t\twantColumns:          []string{\"platypus\", \"rhinoceros\"},\n\t\t\twantColumnsWriteable: []string{\"platypus\", \"rhinoceros\"},\n\t\t},\n\t\t{\n\t\t\tname:                 \"menagerie1\",\n\t\t\tdatatype:             &menagerie1{},\n\t\t\twantColumns:          []string{\"human\", \"flamingo\"},\n\t\t\twantColumnsWriteable: []string{\"human\"},\n\t\t},\n\t}\n\n\tfor _, c := range testCases {\n\t\tif a, b := c.wantColumns, sx.Columns(c.datatype); !reflect.DeepEqual(a, b) {\n\t\t\tt.Errorf(\"case %s columns: expected %v, got %v\", c.name, a, b)\n\t\t}\n\t\tif a, b := c.wantColumnsWriteable, sx.ColumnsWriteable(c.datatype); !reflect.DeepEqual(a, b) {\n\t\t\tt.Errorf(\"case %s columns writeable: expected %v, got %v\", c.name, a, b)\n\t\t}\n\t}\n}\n\nfunc TestColumnOf(t *testing.T) {\n\n\tvar testCases = []struct {\n\t\tname       string\n\t\tdatatype   interface{}\n\t\tfield      string\n\t\twantColumn string\n\t\twantErr    string\n\t}{\n\t\t{\n\t\t\tname:       \"platypus\",\n\t\t\tdatatype:   &menagerie0{},\n\t\t\tfield:      \"Platypus\",\n\t\t\twantColumn: \"platypus\",\n\t\t},\n\t\t{\n\t\t\tname:       \"rhinoceros\",\n\t\t\tdatatype:   &menagerie0{},\n\t\t\tfield:      \"Rhinoceros\",\n\t\t\twantColumn: \"rhinoceros\",\n\t\t},\n\t\t{\n\t\t\tname:     \"unknown column\",\n\t\t\tdatatype: &menagerie0{},\n\t\t\tfield:    \"Hippopotamus\",\n\t\t\twantErr:  \"struct menagerie0 has no usable field Hippopotamus\",\n\t\t},\n\t\t{\n\t\t\tname:       \"chimpanzee\",\n\t\t\tdatatype:   &menagerie1{},\n\t\t\tfield:      \"Chimpanzee\",\n\t\t\twantColumn: \"human\",\n\t\t},\n\t\t{\n\t\t\tname:       \"flamingo\",\n\t\t\tdatatype:   &menagerie1{},\n\t\t\tfield:      \"Flamingo\",\n\t\t\twantColumn: \"flamingo\",\n\t\t},\n\t\t{\n\t\t\tname:     \"ignored field\",\n\t\t\tdatatype: &menagerie1{},\n\t\t\tfield:    \"Warthog\",\n\t\t\twantErr:  \"struct menagerie1 has no usable field Warthog\",\n\t\t},\n\t}\n\n\tfor _, c := range testCases {\n\t\tcolumn, err := sx.ColumnOf(c.datatype, c.field)\n\t\tif err != nil {\n\t\t\tif c.wantErr == \"\" {\n\t\t\t\tt.Errorf(\"case %s: unexpected error %q\", c.name, err.Error())\n\t\t\t} else if a, b := c.wantErr, err.Error(); a != b {\n\t\t\t\tt.Errorf(\"case %s: expected error %q, got %q\", c.name, a, b)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif a, b := c.wantColumn, column; a != b {\n\t\t\tt.Errorf(\"case %s: expected %v, got %v\", c.name, a, b)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "matching.go",
    "content": "package sx\n\nimport (\n\t\"reflect\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n)\n\n// A matching is between struct fields and database columns.\ntype matching struct {\n\treflectType reflect.Type\n\tcolumns     []*column          // an ordered list of columns\n\tcolumnMap   map[string]*column // columns keyed by field name\n}\n\ntype column struct {\n\tindex    int    // index of this field in the struct\n\tname     string // name of the corresponding db column\n\treadonly bool   // flag to skip this column on insert/update operations (e.g. for primary key or automatic timestamp)\n}\n\n// ColumnList returns the names of the database columns in the order of the struct.\nfunc (m *matching) columnList() []string {\n\tlist := make([]string, 0, len(m.columns))\n\tfor _, c := range m.columns {\n\t\tlist = append(list, c.name)\n\t}\n\treturn list\n}\n\n// ColumnWriteableList returns the names of the database columns in the order of the struct, excluding read-only columns.\nfunc (m *matching) columnWriteableList() []string {\n\tlist := make([]string, 0, len(m.columns))\n\tfor _, c := range m.columns {\n\t\tif !c.readonly {\n\t\t\tlist = append(list, c.name)\n\t\t}\n\t}\n\treturn list\n}\n\n// ColumnOf returns the column which matches the named field.  Panics if the field doesn't exist.\nfunc (m *matching) columnOf(field string) *column {\n\tif c, ok := m.columnMap[field]; ok {\n\t\treturn c\n\t}\n\tpanic(\"sx: struct \" + m.reflectType.Name() + \" has no usable field \" + field)\n}\n\n// MatchingOf returns a matching for the given struct type, generating it if necessary.  MatchingOf looks only at the\n// structure of datatype and ignore its values.\n//\n// Panics if datatype does not point at a struct, or if the struct has no usable fields.\nfunc matchingOf(datatype interface{}) *matching {\n\tmatchingCacheMu.Lock()\n\tdefer matchingCacheMu.Unlock()\n\n\tv := reflect.ValueOf(datatype)\n\tif v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {\n\t\tpanic(\"sx: expected a pointer to a struct\")\n\t}\n\n\t// First look for a cached matching.\n\treflectType := v.Elem().Type()\n\tif m, ok := matchingCache[reflectType]; ok {\n\t\treturn m\n\t}\n\n\t// Nothing cached, generate a new matching and cache it.\n\tn := reflectType.NumField()\n\tcols := make([]*column, 0)\n\tcolmap := make(map[string]*column)\n\tfor i := 0; i < n; i++ {\n\t\tfield := reflectType.Field(i)\n\t\ttags := strings.Split(field.Tag.Get(\"sx\"), \",\")\n\t\tcolname := tags[0]\n\t\tif colname == \"-\" || field.PkgPath != \"\" {\n\t\t\tcontinue // skip excluded and unexported fields.\n\t\t}\n\t\tif colname == \"\" {\n\t\t\tcolname = snakeCase(field.Name) // default column name based on field name\n\t\t}\n\t\tcol := &column{\n\t\t\tindex: i,\n\t\t\tname:  colname,\n\t\t}\n\t\t// See if there's a readonly tag.  A readonly tag would have to be in at least the second position, since\n\t\t// the first position is always interpreted as a column name.\n\t\tfor _, tag := range tags[1:] {\n\t\t\tif tag == \"readonly\" {\n\t\t\t\tcol.readonly = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tcols = append(cols, col)\n\t\tcolmap[field.Name] = col\n\t}\n\tif len(cols) == 0 {\n\t\tpanic(\"sx: struct \" + reflectType.Name() + \" has no usable fields\")\n\t}\n\n\tm := &matching{\n\t\treflectType: reflectType,\n\t\tcolumns:     cols,\n\t\tcolumnMap:   colmap,\n\t}\n\tmatchingCache[reflectType] = m\n\treturn m\n}\n\n// Cache to keep track of struct types that have been seen and therefore analyzed.\nvar matchingCache = make(map[reflect.Type]*matching)\nvar matchingCacheMu sync.Mutex\n\n// Snake-casing logic.\n\nvar (\n\tmatchWord    = regexp.MustCompile(`(.)([A-Z][a-z]+)`)\n\tmatchAcronym = regexp.MustCompile(`([a-z0-9])([A-Z])`)\n)\n\nfunc snakeCase(in string) string {\n\tconst r = `${1}_${2}`\n\treturn strings.ToLower(matchAcronym.ReplaceAllString(matchWord.ReplaceAllString(in, r), r))\n}\n"
  },
  {
    "path": "matching_test.go",
    "content": "package sx_test\n\nimport (\n\t\"testing\"\n\n\tsx \"github.com/travelaudience/go-sx\"\n)\n\n// The tests in this file test that the correct panics are generated.  The tests in helpers_test.go test for\n// the correct results.\n\nfunc TestMatching(t *testing.T) {\n\n\ttype test1 struct {\n\t\tA           int\n\t\tLollipop    bool\n\t\tChocolateID float64\n\t\tFOOBarBAZ   string\n\t}\n\n\ttype test2 struct {\n\t\tA int `sx:\"-\"`\n\t}\n\n\ttype test3 struct {\n\t}\n\n\ttype test4 struct {\n\t\tA int `sx:\"-\"`\n\t\tB int `sx:\"foo\"`\n\t\tC int `sx:\"bar\"`\n\t\t_ int `sx:\"baz\"`\n\t}\n\n\tt.Run(\"panics on bad input\", func(t *testing.T) {\n\n\t\tvar testCases = []struct {\n\t\t\tname      string\n\t\t\tdata      interface{}\n\t\t\twantPanic string\n\t\t}{\n\t\t\t{\n\t\t\t\tname:      \"pass a struct, not a pointer\",\n\t\t\t\tdata:      test1{},\n\t\t\t\twantPanic: \"sx: expected a pointer to a struct\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:      \"pass nil\",\n\t\t\t\tdata:      nil,\n\t\t\t\twantPanic: \"sx: expected a pointer to a struct\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:      \"pass something else\",\n\t\t\t\tdata:      \"hello\",\n\t\t\t\twantPanic: \"sx: expected a pointer to a struct\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:      \"no usable fields\",\n\t\t\t\tdata:      &test2{},\n\t\t\t\twantPanic: \"sx: struct test2 has no usable fields\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:      \"no exported fields\",\n\t\t\t\tdata:      &test3{},\n\t\t\t\twantPanic: \"sx: struct test3 has no usable fields\",\n\t\t\t},\n\t\t}\n\n\t\tfor _, c := range testCases {\n\t\t\tfunc() {\n\t\t\t\tdefer func() {\n\t\t\t\t\tr := recover()\n\t\t\t\t\tif r == nil {\n\t\t\t\t\t\tt.Errorf(\"case %s: expected a panic\", c.name)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif s, ok := r.(string); ok {\n\t\t\t\t\t\tif s != c.wantPanic {\n\t\t\t\t\t\t\tt.Errorf(\"case %s: expected \\\"%s\\\", got \\\"%s\\\"\", c.name, c.wantPanic, s)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tpanic(r)\n\t\t\t\t}()\n\t\t\t\t// this calls matchingOf(c.data) straight away\n\t\t\t\tsx.Values(c.data)\n\t\t\t}()\n\t\t}\n\t})\n\n\tt.Run(\"ColumnOf panics on unknown field\", func(t *testing.T) {\n\n\t\tvar testCases = []struct {\n\t\t\tname      string\n\t\t\tdata      interface{}\n\t\t\tfield     string\n\t\t\twantPanic string\n\t\t}{\n\t\t\t{\n\t\t\t\tname:      \"unknown field\",\n\t\t\t\tdata:      &test1{},\n\t\t\t\tfield:     \"Zzzzz\",\n\t\t\t\twantPanic: \"sx: struct test1 has no usable field Zzzzz\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:      \"ignored field\",\n\t\t\t\tdata:      &test4{},\n\t\t\t\tfield:     \"A\",\n\t\t\t\twantPanic: \"sx: struct test4 has no usable field A\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:      \"unexported field\",\n\t\t\t\tdata:      &test4{},\n\t\t\t\tfield:     \"d\",\n\t\t\t\twantPanic: \"sx: struct test4 has no usable field d\",\n\t\t\t},\n\t\t}\n\n\t\tfor _, c := range testCases {\n\t\t\tfunc() {\n\t\t\t\tdefer func() {\n\t\t\t\t\tr := recover()\n\t\t\t\t\tif r == nil {\n\t\t\t\t\t\tt.Errorf(\"case %s: expected a panic\", c.name)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif s, ok := r.(string); ok {\n\t\t\t\t\t\tif s != c.wantPanic {\n\t\t\t\t\t\t\tt.Errorf(\"case %s: expected %q, got %q\", c.name, c.wantPanic, s)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tpanic(r)\n\t\t\t\t}()\n\t\t\t\t// this calls matchingOf(c.data).ColumnOf(c.field)\n\t\t\t\tsx.ValueOf(c.data, c.field)\n\t\t\t}()\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "placeholder.go",
    "content": "package sx\n\nimport \"strconv\"\n\nvar numberedPlaceholders bool\n\n// SetNumberedPlaceholders sets the style of placeholders to be used for generated queries.  If yes is true, then\n// postgres-style \"$n\" placeholders will be used for all future queries.  If yes is false, then mysql-style \"?\"\n// placeholders will be used.  This setting may be changed at any time.  Default is false.\nfunc SetNumberedPlaceholders(yes bool) {\n\tnumberedPlaceholders = yes\n}\n\n// A Placeholder is a generator for the currently selected placeholder type.  See SetNumberedPlaceholders().\ntype Placeholder int\n\n// String displays the current placeholder value in its chosen format (either \"?\" or \"$n\").\nfunc (p Placeholder) String() string {\n\tif numberedPlaceholders {\n\t\treturn \"$\" + strconv.Itoa(int(p))\n\t}\n\treturn \"?\"\n}\n\n// Next increments the placeholder value and returns the string value of the next placeholder in sequence.\n//\n// When using numbered placeholders, a zero-valued placeholder will return \"$1\" on its first call to Next().\n// When using ?-style placeholders, Next always returns \"?\".\nfunc (p *Placeholder) Next() string {\n\t*p++\n\treturn p.String()\n}\n"
  },
  {
    "path": "placeholder_test.go",
    "content": "package sx_test\n\nimport (\n\t\"testing\"\n\n\tsx \"github.com/travelaudience/go-sx\"\n)\n\nfunc TestPlaceholders(t *testing.T) {\n\n\tsx.SetNumberedPlaceholders(false)\n\n\tt.Run(\"? placeholders\", func(t *testing.T) {\n\t\twant := []string{\"?\", \"?\", \"?\"}\n\t\tvar p sx.Placeholder\n\n\t\tfor i, x := range want {\n\t\t\ty := p.Next()\n\t\t\tif x != y {\n\t\t\t\tt.Errorf(\"case a-%d: expected %s, got %s\", i, x, y)\n\t\t\t}\n\t\t}\n\t})\n\n\tsx.SetNumberedPlaceholders(true)\n\n\tt.Run(\"numbered placeholders\", func(t *testing.T) {\n\t\twant := []string{\"$1\", \"$2\", \"$3\"}\n\t\tvar p sx.Placeholder\n\n\t\tfor i, x := range want {\n\t\t\ty := p.Next()\n\t\t\tif x != y {\n\t\t\t\tt.Errorf(\"case b-%d: expected %s, got %s\", i, x, y)\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "tx.go",
    "content": "package sx\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n)\n\n// Tx extends sql.Tx with some Must*** methods that panic instead of returning an error code.  Tx objects are used\n// inside of transactions managed by Do.  Panics are caught by Do and returned as errors.\ntype Tx struct {\n\t*sql.Tx\n}\n\n// An sxError is used to wrap errors that we want to send back to the caller of Do.\ntype sxError struct {\n\terr error\n}\n\n// MustExec executes a query without returning any rows.  The args are for any placeholder parameters in the query.\n// In case of error, the transaction is aborted and Do returns the error code.\nfunc (tx *Tx) MustExec(query string, args ...interface{}) sql.Result {\n\treturn tx.MustExecContext(context.Background(), query, args...)\n}\n\n// MustExecContext executes a query without returning any rows.  The args are for any placeholder parameters in the\n// query.  In case of error, the transaction is aborted and Do returns the error code.\nfunc (tx *Tx) MustExecContext(ctx context.Context, query string, args ...interface{}) sql.Result {\n\tres, err := tx.ExecContext(ctx, query, args...)\n\tif err != nil {\n\t\tpanic(sxError{err})\n\t}\n\treturn res\n}\n\n// MustQuery executes a query that returns rows.  The args are for any placeholder parameters in the query.\n// In case of error, the transaction is aborted and Do returns the error code.\nfunc (tx *Tx) MustQuery(query string, args ...interface{}) *Rows {\n\treturn tx.MustQueryContext(context.Background(), query, args...)\n}\n\n// MustQueryContext executes a query that returns rows.  The args are for any placeholder parameters in the query.\n// In case of error, the transaction is aborted and Do returns the error code.\nfunc (tx *Tx) MustQueryContext(ctx context.Context, query string, args ...interface{}) *Rows {\n\trows, err := tx.QueryContext(ctx, query, args...)\n\tif err != nil {\n\t\tpanic(sxError{err})\n\t}\n\treturn &Rows{rows}\n}\n\n// MustQueryRow executes a query that is expected to return at most one row.  MustQueryRow always returns a non-nil\n// value.  Errors are deferred until one of the Row's scan methods is called.\nfunc (tx *Tx) MustQueryRow(query string, args ...interface{}) *Row {\n\treturn &Row{tx.QueryRowContext(context.Background(), query, args...)}\n}\n\n// MustQueryRowContext executes a query that is expected to return at most one row.  MustQueryRow always returns a\n// non-nil value.  Errors are deferred until one of the Row's scan methods is called.\nfunc (tx *Tx) MustQueryRowContext(ctx context.Context, query string, args ...interface{}) *Row {\n\treturn &Row{tx.QueryRowContext(ctx, query, args...)}\n}\n\n// MustPrepare creates a prepared statement for later queries or executions.  Multiple queries or executions may be\n// run concurrently from the returned statement.  In case of error, the transaction is aborted and Do returns the\n// error code.\n//\n// The caller must call the statement's Close method when the statement is no longer needed.\nfunc (tx *Tx) MustPrepare(query string) *Stmt {\n\treturn tx.MustPrepareContext(context.Background(), query)\n}\n\n// MustPrepareContext creates a prepared statement for later queries or executions.  Multiple queries or executions\n// may be run concurrently from the returned statement.  In case of error, the transaction is aborted and Do returns\n// the error code.\n//\n// The caller must call the statement's Close method when the statement is no longer needed.\nfunc (tx *Tx) MustPrepareContext(ctx context.Context, query string) *Stmt {\n\tstmt, err := tx.PrepareContext(ctx, query)\n\tif err != nil {\n\t\tpanic(sxError{err})\n\t}\n\treturn &Stmt{stmt}\n}\n\n// Fail aborts and rolls back the transaction, returning the given error code to the caller of Do.  Fail always\n// rolls back the transaction, even if err is nil.\nfunc (tx *Tx) Fail(err error) {\n\tpanic(sxError{err})\n}\n\n// Stmt extends sql.Stmt with some Must*** methods that panic instead of returning an error code.  Stmt objects are\n// used inside of transactions managed by Do.  Panics are caught by Do and returned as errors.\ntype Stmt struct {\n\t*sql.Stmt\n}\n\n// MustExec executes a prepared statement with the given arguments and returns an sql.Result summarizing the effect\n// of the statement.  In case of error, the transaction is aborted and Do returns the error code.\nfunc (stmt *Stmt) MustExec(args ...interface{}) sql.Result {\n\treturn stmt.MustExecContext(context.Background(), args...)\n}\n\n// MustExecContext executes a prepared statement with the given arguments and returns an sql.Result summarizing the\n// effect of the statement.  In case of error, the transaction is aborted and Do returns the error code.\nfunc (stmt *Stmt) MustExecContext(ctx context.Context, args ...interface{}) sql.Result {\n\tres, err := stmt.ExecContext(ctx, args...)\n\tif err != nil {\n\t\tpanic(sxError{err})\n\t}\n\treturn res\n}\n\n// MustQuery executes a prepared query statement with the given arguments and returns the query results as a *Rows.\n// In case of error, the transaction is aborted and Do returns the error code.\nfunc (stmt *Stmt) MustQuery(args ...interface{}) *Rows {\n\treturn stmt.MustQueryContext(context.Background(), args...)\n}\n\n// MustQueryContext executes a prepared query statement with the given arguments and returns the query results as\n// a *Rows.  In case of error, the transaction is aborted and Do returns the error code.\nfunc (stmt *Stmt) MustQueryContext(ctx context.Context, args ...interface{}) *Rows {\n\trows, err := stmt.QueryContext(ctx, args...)\n\tif err != nil {\n\t\tpanic(sxError{err})\n\t}\n\treturn &Rows{rows}\n}\n\n// MustQueryRow executes a prepared query that is expected to return at most one row.  MustQueryRow always returns\n// a non-nil value.  Errors are deferred until one of the Row's scan methods is called.\nfunc (stmt *Stmt) MustQueryRow(args ...interface{}) *Row {\n\treturn &Row{stmt.QueryRowContext(context.Background(), args...)}\n}\n\n// MustQueryRowContext executes a prepared query that is expected to return at most one row.  MustQueryRowContext\n// always returns a non-nil value.  Errors are deferred until one of the Row's scan methods is called.\nfunc (stmt *Stmt) MustQueryRowContext(ctx context.Context, args ...interface{}) *Row {\n\treturn &Row{stmt.QueryRowContext(ctx, args...)}\n}\n\n// Do runs a callback function f, providing f with the prepared statement, and then closing the prepared statement\n// after f returns.\nfunc (stmt *Stmt) Do(f func(*Stmt)) {\n\tdefer stmt.Close()\n\tf(stmt)\n}\n\n// Row is the result of calling MustQueryRow to select a single row.  Row extends sql.Row with some useful\n// scan methods.\ntype Row struct {\n\t*sql.Row\n}\n\n// MustScan copies the columns in the current row into the values pointed at by dest.  In case of error, the\n// transaction is aborted and Do returns the error code.\nfunc (row *Row) MustScan(dest ...interface{}) {\n\terr := row.Scan(dest...)\n\tif err != nil {\n\t\tpanic(sxError{err})\n\t}\n}\n\n// MustScans copies the columns in the current row into the struct pointed at by dest.  In case of error, the\n// transaction is aborted and Do returns the error code.\nfunc (row *Row) MustScans(dest interface{}) {\n\trow.MustScan(Addrs(dest)...)\n}\n\n// Rows is the result of calling MustQuery to select a set of rows.  Rows extends sql.Rows with some useful\n// scan methods.\ntype Rows struct {\n\t*sql.Rows\n}\n\n// MustScan calls Scan to read in a row of the result set.  In case of error, the transaction is aborted and Do\n// returns the error code.\nfunc (rows *Rows) MustScan(dest ...interface{}) {\n\terr := rows.Scan(dest...)\n\tif err != nil {\n\t\tpanic(sxError{err})\n\t}\n}\n\n// MustScans copies the columns in the current row into the struct pointed at by dest.  In case of error, the\n// transaction is aborted and Do returns the error code.\nfunc (rows *Rows) MustScans(dest interface{}) {\n\trows.MustScan(Addrs(dest)...)\n}\n\n// Each iterates over all of the rows in a result set and runs a callback function on each row.\nfunc (rows *Rows) Each(f func(*Rows)) {\n\tdefer rows.Close()\n\tfor rows.Next() {\n\t\tf(rows)\n\t}\n\terr := rows.Err()\n\tif err != nil {\n\t\tpanic(sxError{err})\n\t}\n}\n\n// Do runs the function f in a transaction.  Within f, if Fail() is invoked or if any Must*** method encounters\n// an error, then the transaction is rolled back and Do returns the error.  If f runs to completion, then the\n// transaction is committed, and Do returns nil.\n//\n// Internally, the Must*** methods panic on error, and Fail() always panics.  The panic aborts execution of f.\n// f should not attempt to recover from the panic.  Instead, Do will catch the panic and return it as an error.\n//\n// A TxOptions may be provided to specify isolation level and/or read-only status.  If no TxOptions is provided,\n// then the default oprtions are used.  Extra TxOptions are ignored.\nfunc Do(db *sql.DB, f func(*Tx), opts ...sql.TxOptions) error {\n\treturn DoContext(context.Background(), db, f, opts...)\n}\n\n// DoContext runs the function f in a transaction.  Within f, if Fail() is invoked or if any Must*** method encounters\n// an error, then the transaction is rolled back and Do returns the error.  If f runs to completion, then the\n// transaction is committed, and DoContext returns nil.\n//\n// Internally, the Must*** methods panic on error, and Fail() always panics.  The panic aborts execution of f.\n// f should not attempt to recover from the panic.  Instead, Do will catch the panic and return it as an error.\n//\n// A TxOptions may be provided to specify isolation level and/or read-only status.  If no TxOptions is provided,\n// then the default oprtions are used.  Extra TxOptions are ignored.\nfunc DoContext(ctx context.Context, db *sql.DB, f func(*Tx), opts ...sql.TxOptions) (err error) {\n\n\tvar opt *sql.TxOptions\n\tif len(opts) > 0 {\n\t\topt = &opts[0]\n\t}\n\n\tvar tx *sql.Tx\n\ttx, err = db.BeginTx(ctx, opt)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tif ourerr, ok := r.(sxError); ok {\n\t\t\t\t// Our panic.  Unwrap it and return it as an error code.\n\t\t\t\ttx.Rollback()\n\t\t\t\terr = ourerr.err\n\t\t\t} else {\n\t\t\t\t// Not our panic, so propagate it.\n\t\t\t\tpanic(r)\n\t\t\t}\n\t\t}\n\t}()\n\n\t// This runs the queries.\n\tf(&Tx{tx})\n\n\terr = tx.Commit()\n\treturn\n}\n"
  },
  {
    "path": "tx_test.go",
    "content": "package sx_test\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"math/rand\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\tsqlmock \"github.com/DATA-DOG/go-sqlmock\"\n\n\tsx \"github.com/travelaudience/go-sx\"\n)\n\nfunc TestMain(m *testing.M) {\n\tos.Exit(m.Run())\n}\n\n// helper functions\n\nfunc newMock(t *testing.T) (*sql.DB, sqlmock.Sqlmock) {\n\tt.Helper()\n\tdb, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))\n\tif err != nil {\n\t\tt.Fatalf(\"error creating mock database: %v\", err)\n\t}\n\treturn db, mock\n}\n\nfunc endMock(t *testing.T, mock sqlmock.Sqlmock) {\n\tt.Helper()\n\terr := mock.ExpectationsWereMet()\n\tif err != nil {\n\t\tt.Errorf(\"mocked expectations were not met: %v\", err)\n\t}\n}\n\nfunc TestMustExec(t *testing.T) {\n\n\tt.Run(\"MustExec with result\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\ta, b := rand.Int63(), rand.Int63()\n\t\tconst query = \"SELECT alpha\"\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectExec(query).WillReturnResult(sqlmock.NewResult(a, b))\n\t\tmock.ExpectCommit()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\tres := tx.MustExec(query)\n\t\t\ta0, _ := res.LastInsertId()\n\t\t\tb0, _ := res.RowsAffected()\n\t\t\tif a0 != a || b0 != b {\n\t\t\t\tt.Errorf(\"Expected result (%d, %d), got (%d, %d)\", a, b, a0, b0)\n\t\t\t}\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustExec with error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tconst query = \"SELECT bravo\"\n\t\terr0 := errors.New(\"bravo error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectExec(query).WillReturnError(err0)\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustExec(query)\n\t\t})\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected error %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustExec with 1 argument and result\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tx, a, b := rand.Int63(), rand.Int63(), rand.Int63()\n\t\tconst query = \"SELECT charlie\"\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectExec(query).WithArgs(x).WillReturnResult(sqlmock.NewResult(a, b))\n\t\tmock.ExpectCommit()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\tres := tx.MustExec(query, x)\n\t\t\ta0, _ := res.LastInsertId()\n\t\t\tb0, _ := res.RowsAffected()\n\t\t\tif a0 != a || b0 != b {\n\t\t\t\tt.Errorf(\"Expected result (%d, %d), got (%d, %d)\", a, b, a0, b0)\n\t\t\t}\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustExec with 2 arguments and error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tx, y := rand.Int63(), rand.Int63()\n\t\tconst query = \"SELECT delta\"\n\t\terr0 := errors.New(\"delta error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectExec(query).WithArgs(x, y).WillReturnError(err0)\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustExec(query, x, y)\n\t\t})\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected error %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustExecContext with result\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\ta, b := rand.Int63(), rand.Int63()\n\t\tconst query = \"SELECT alpha_context\"\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectExec(query).WillReturnResult(sqlmock.NewResult(a, b))\n\t\tmock.ExpectCommit()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\tres := tx.MustExecContext(context.Background(), query)\n\t\t\ta0, _ := res.LastInsertId()\n\t\t\tb0, _ := res.RowsAffected()\n\t\t\tif a0 != a || b0 != b {\n\t\t\t\tt.Errorf(\"Expected result (%d, %d), got (%d, %d)\", a, b, a0, b0)\n\t\t\t}\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustExec with isolation level and error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tconst query = \"SELECT bravissimo\"\n\t\terr0 := errors.New(\"bravissimo error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectExec(query).WillReturnError(err0)\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustExec(query)\n\t\t}, sql.TxOptions{Isolation: sql.LevelSerializable})\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected error %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n}\n\nfunc TestMustQueryRow(t *testing.T) {\n\n\tt.Run(\"MustQueryRow with result\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\ta, b := rand.Int63(), rand.Int63()\n\t\tconst query = \"SELECT echo\"\n\t\trows := sqlmock.NewRows([]string{\"a\", \"b\"}).AddRow(a, b)\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectQuery(query).WillReturnRows(rows)\n\t\tmock.ExpectCommit()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\tvar a0, b0 int64\n\t\t\ttx.MustQueryRow(query).MustScan(&a0, &b0)\n\t\t\tif a0 != a || b0 != b {\n\t\t\t\tt.Errorf(\"Expected result (%d, %d), got (%d, %d)\", a, b, a0, b0)\n\t\t\t}\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustQueryRow with no rows\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tconst query = \"SELECT foxtrot\"\n\t\trows := sqlmock.NewRows([]string{\"a\", \"b\"})\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectQuery(query).WillReturnRows(rows)\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\tvar a0, b0 int64\n\t\t\ttx.MustQueryRow(query).MustScan(&a0, &b0)\n\t\t})\n\t\tif err != sql.ErrNoRows {\n\t\t\tt.Errorf(\"expected error %v, got %v\", sql.ErrNoRows, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustQueryRow with error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tconst query = \"SELECT golf\"\n\t\terr0 := errors.New(\"golf error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectQuery(query).WillReturnError(err0)\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\tvar a0, b0 int64\n\t\t\ttx.MustQueryRow(query).MustScan(&a0, &b0)\n\t\t})\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected error %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustQueryRow with 1 argument and error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tx := rand.Int63()\n\t\tconst query = \"SELECT hotel\"\n\t\terr0 := errors.New(\"hotel error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectQuery(query).WithArgs(x).WillReturnError(err0)\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\tvar a0, b0 int64\n\t\t\ttx.MustQueryRow(query, x).MustScan(&a0, &b0)\n\t\t})\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected error %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustQueryRow with 3 arguments and struct result\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\ta, b, x, y, z := rand.Int63(), rand.Int63(), rand.Int63(), rand.Int63(), rand.Int63()\n\t\tconst query = \"SELECT indigo\"\n\t\trows := sqlmock.NewRows([]string{\"a\", \"b\"}).AddRow(a, b)\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectQuery(query).WithArgs(x, y, z).WillReturnRows(rows)\n\t\tmock.ExpectCommit()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\tvar res struct{ A, B int64 }\n\t\t\ttx.MustQueryRow(query, x, y, z).MustScans(&res)\n\t\t\tif res.A != a || res.B != b {\n\t\t\t\tt.Errorf(\"Expected result (%d, %d), got (%d, %d)\", a, b, res.A, res.B)\n\t\t\t}\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustQueryRowContext with result\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\ta, b := rand.Int63(), rand.Int63()\n\t\tconst query = \"SELECT echo_context\"\n\t\trows := sqlmock.NewRows([]string{\"a\", \"b\"}).AddRow(a, b)\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectQuery(query).WillReturnRows(rows)\n\t\tmock.ExpectCommit()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\tvar a0, b0 int64\n\t\t\ttx.MustQueryRowContext(context.TODO(), query).MustScan(&a0, &b0)\n\t\t\tif a0 != a || b0 != b {\n\t\t\t\tt.Errorf(\"Expected result (%d, %d), got (%d, %d)\", a, b, a0, b0)\n\t\t\t}\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n}\n\nfunc TestMustQuery(t *testing.T) {\n\n\tt.Run(\"MustQuery with error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tconst query = \"SELECT juliett\"\n\t\terr0 := errors.New(\"juliett error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectQuery(query).WillReturnError(err0)\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustQuery(query)\n\t\t})\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected error %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustQuery with 1 argument and error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tx := rand.Int63()\n\t\tconst query = \"SELECT kilo\"\n\t\terr0 := errors.New(\"kilo error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectQuery(query).WithArgs(x).WillReturnError(err0)\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustQuery(query, x)\n\t\t})\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected error %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustQuery with 1 argument and 1 result row\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\ta, b, x := rand.Int63(), rand.Int63(), rand.Int63()\n\t\tconst query = \"SELECT lima\"\n\t\trows := sqlmock.NewRows([]string{\"a\", \"b\"}).AddRow(a, b)\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectQuery(query).WithArgs(x).WillReturnRows(rows)\n\t\tmock.ExpectCommit()\n\n\t\tvar a0, b0 int64\n\t\tn := 0\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustQuery(query, x).Each(func(r *sx.Rows) {\n\t\t\t\tr.MustScan(&a0, &b0)\n\t\t\t\tn++\n\t\t\t})\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif n != 1 {\n\t\t\tt.Errorf(\"Expected 1 row, got %d\", n)\n\t\t} else if a0 != a || b0 != b {\n\t\t\tt.Errorf(\"Expected result (%d, %d), got (%d, %d)\", a, b, a0, b0)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustQuery with 1 argument and 1 result row with error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tx := rand.Int63()\n\t\tconst query = \"SELECT mike\"\n\t\trows := sqlmock.NewRows([]string{\"a\", \"b\"}).AddRow(\"scan\", \"error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectQuery(query).WithArgs(x).WillReturnRows(rows)\n\t\tmock.ExpectRollback()\n\n\t\tvar a0, b0 int64\n\t\tn := 0\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustQuery(query, x).Each(func(r *sx.Rows) {\n\t\t\t\tr.MustScan(&a0, &b0)\n\t\t\t\tn++\n\t\t\t})\n\t\t})\n\t\tif n != 0 {\n\t\t\tt.Errorf(\"Expected no rows, got %d\", n)\n\t\t} else if err == nil || !strings.Contains(err.Error(), \"Scan error\") {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustQuery with 1 argument and 2 struct result rows\", func(t *testing.T) {\n\t\ttype ab struct{ A, B int64 }\n\n\t\tdb, mock := newMock(t)\n\t\tdat, x := [2]ab{{A: rand.Int63(), B: rand.Int63()}, {A: rand.Int63(), B: rand.Int63()}}, rand.Int63()\n\t\tconst query = \"SELECT november\"\n\t\trows := sqlmock.NewRows([]string{\"a\", \"b\"}).AddRow(dat[0].A, dat[0].B).AddRow(dat[1].A, dat[1].B)\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectQuery(query).WithArgs(x).WillReturnRows(rows)\n\t\tmock.ExpectCommit()\n\n\t\tvar res [2]ab\n\t\tn := 0\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustQuery(query, x).Each(func(r *sx.Rows) {\n\t\t\t\tr.MustScans(&res[n])\n\t\t\t\tn++\n\t\t\t})\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif n != 2 {\n\t\t\tt.Errorf(\"Expected 2 rows, got %d\", n)\n\t\t} else if res != dat {\n\t\t\tt.Errorf(\"Expected results (%d, %d), (%d, %d) got (%d, %d), (%d, %d)\",\n\t\t\t\tdat[0].A, dat[0].B, dat[1].A, dat[1].B, res[0].A, res[0].B, res[1].A, res[1].B)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustQuery with 2 arguments, 2 result rows and row error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\ta, b, x, y := [2]int64{rand.Int63(), 0}, [2]int64{rand.Int63(), 0}, rand.Int63(), rand.Int63()\n\t\tconst query = \"SELECT oscar\"\n\t\terr0 := errors.New(\"oscar error\")\n\t\trows := sqlmock.NewRows([]string{\"a\", \"b\"}).AddRow(a[0], b[0]).AddRow(a[1], b[1]).RowError(1, err0)\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectQuery(query).WithArgs(x, y).WillReturnRows(rows)\n\t\tmock.ExpectRollback()\n\n\t\tvar aa, bb [2]int64\n\t\tn := 0\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustQuery(query, x, y).Each(func(r *sx.Rows) {\n\t\t\t\tr.MustScan(&aa[n], &bb[n])\n\t\t\t\tn++\n\t\t\t})\n\t\t})\n\t\tif n != 1 {\n\t\t\tt.Errorf(\"Expected 1 row before the row error, got %d\", n)\n\t\t} else if err != err0 {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t} else if aa != a || bb != b {\n\t\t\tt.Errorf(\"Expected result (%d, %d) before the row error, got (%d, %d)\",\n\t\t\t\ta[0], b[0], aa[0], bb[0])\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustQuery with 2 arguments, 2 result rows and scan error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\ta, b := [3]int64{rand.Int63(), rand.Int63(), 0}, [3]int64{rand.Int63(), rand.Int63(), 0}\n\t\tx, y := rand.Int63(), rand.Int63()\n\t\tconst query = \"SELECT papa\"\n\t\trows := sqlmock.NewRows([]string{\"a\", \"b\"}).AddRow(a[0], b[0]).AddRow(a[1], b[1]).AddRow(\"scan\", \"error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectQuery(query).WithArgs(x, y).WillReturnRows(rows)\n\t\tmock.ExpectRollback()\n\n\t\tvar aa, bb [3]int64\n\t\tn := 0\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustQuery(query, x, y).Each(func(r *sx.Rows) {\n\t\t\t\tr.MustScan(&aa[n], &bb[n])\n\t\t\t\tn++\n\t\t\t})\n\t\t})\n\t\tif n != 2 {\n\t\t\tt.Errorf(\"Expected 2 rows before the scan error, got %d\", n)\n\t\t} else if err == nil || !strings.Contains(err.Error(), \"Scan error\") {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t} else if aa != a || bb != b {\n\t\t\tt.Errorf(\"Expected results (%d, %d), (%d, %d) before the scan error, got (%d, %d), (%d, %d)\",\n\t\t\t\ta[0], b[0], a[1], b[1], aa[0], bb[0], aa[1], bb[1])\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustQueryContext with error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tconst query = \"SELECT juliett_context\"\n\t\terr0 := errors.New(\"juliett_context error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectQuery(query).WillReturnError(err0)\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustQueryContext(context.TODO(), query)\n\t\t})\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected error %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n}\n\nfunc TestMustPrepare(t *testing.T) {\n\n\tt.Run(\"MustPrepare with MustExec and result\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\ta, b, x := rand.Int63(), rand.Int63(), rand.Int63()\n\t\tconst query = \"SELECT quebec\"\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectPrepare(query).ExpectExec().WithArgs(x).WillReturnResult(sqlmock.NewResult(a, b))\n\t\tmock.ExpectCommit()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustPrepare(query).Do(func(stmt *sx.Stmt) {\n\t\t\t\tres := stmt.MustExec(x)\n\t\t\t\ta0, _ := res.LastInsertId()\n\t\t\t\tb0, _ := res.RowsAffected()\n\t\t\t\tif a0 != a || b0 != b {\n\t\t\t\t\tt.Errorf(\"Expected result (%d, %d), got (%d, %d)\", a, b, a0, b0)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustPrepare with MustExec and error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tconst query = \"SELECT romeo\"\n\t\terr0 := errors.New(\"romeo error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectPrepare(query).ExpectExec().WillReturnError(err0)\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustPrepare(query).Do(func(stmt *sx.Stmt) {\n\t\t\t\tstmt.MustExec()\n\t\t\t})\n\t\t})\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected error %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustPrepare with MustQueryRow and result\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\ta, b, x := rand.Int63(), rand.Int63(), rand.Int63()\n\t\tconst query = \"SELECT sierra\"\n\t\trows := sqlmock.NewRows([]string{\"a\", \"b\"}).AddRow(a, b)\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectPrepare(query).ExpectQuery().WithArgs(x).WillReturnRows(rows)\n\t\tmock.ExpectCommit()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\tvar a0, b0 int64\n\t\t\ttx.MustPrepare(query).Do(func(stmt *sx.Stmt) {\n\t\t\t\tstmt.MustQueryRow(x).MustScan(&a0, &b0)\n\t\t\t\tif a0 != a || b0 != b {\n\t\t\t\t\tt.Errorf(\"Expected result (%d, %d), got (%d, %d)\", a, b, a0, b0)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustPrepare with MustQueryRow and error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tconst query = \"SELECT tango\"\n\t\terr0 := errors.New(\"tango error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectPrepare(query).ExpectQuery().WillReturnError(err0)\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\tvar a0, b0 int64\n\t\t\ttx.MustPrepare(query).Do(func(stmt *sx.Stmt) {\n\t\t\t\tstmt.MustQueryRow().MustScan(&a0, &b0)\n\t\t\t})\n\t\t})\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected error %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustPrepare with MustQuery and result\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\ta, b, x := rand.Int63(), rand.Int63(), rand.Int63()\n\t\tconst query = \"SELECT uniform\"\n\t\trows := sqlmock.NewRows([]string{\"a\", \"b\"}).AddRow(a, b)\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectPrepare(query).ExpectQuery().WithArgs(x).WillReturnRows(rows)\n\t\tmock.ExpectCommit()\n\n\t\tvar a0, b0 int64\n\t\tn := 0\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustPrepare(query).Do(func(stmt *sx.Stmt) {\n\t\t\t\tstmt.MustQuery(x).Each(func(r *sx.Rows) {\n\t\t\t\t\tr.MustScan(&a0, &b0)\n\t\t\t\t\tn++\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\t\tif n != 1 {\n\t\t\tt.Errorf(\"Expected 1 row, got %d\", n)\n\t\t} else if a0 != a || b0 != b {\n\t\t\tt.Errorf(\"Expected result (%d, %d), got (%d, %d)\", a, b, a0, b0)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustPrepare with MustQuery and error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tconst query = \"SELECT victor\"\n\t\terr0 := errors.New(\"victor error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectPrepare(query).ExpectQuery().WillReturnError(err0)\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustPrepare(query).Do(func(stmt *sx.Stmt) {\n\t\t\t\tstmt.MustQuery()\n\t\t\t})\n\t\t})\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected error %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustPrepare with error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tconst query = \"SELECT whiskey\"\n\t\terr0 := errors.New(\"whiskey error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectPrepare(query).WillReturnError(err0)\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustPrepare(query)\n\t\t})\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected error %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustPrepareContext with MustQueryRowContext and result\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\ta, b, x := rand.Int63(), rand.Int63(), rand.Int63()\n\t\tconst query = \"SELECT sierra_context\"\n\t\trows := sqlmock.NewRows([]string{\"a\", \"b\"}).AddRow(a, b)\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectPrepare(query).ExpectQuery().WithArgs(x).WillReturnRows(rows)\n\t\tmock.ExpectCommit()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\tvar a0, b0 int64\n\t\t\ttx.MustPrepareContext(context.TODO(), query).Do(func(stmt *sx.Stmt) {\n\t\t\t\tstmt.MustQueryRowContext(context.TODO(), x).MustScan(&a0, &b0)\n\t\t\t\tif a0 != a || b0 != b {\n\t\t\t\t\tt.Errorf(\"Expected result (%d, %d), got (%d, %d)\", a, b, a0, b0)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"MustPrepareContext with MustQueryContext and error\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\tconst query = \"SELECT victor_context\"\n\t\terr0 := errors.New(\"victor_context error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectPrepare(query).ExpectQuery().WillReturnError(err0)\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.MustPrepareContext(context.TODO(), query).Do(func(stmt *sx.Stmt) {\n\t\t\t\tstmt.MustQueryContext(context.TODO())\n\t\t\t})\n\t\t})\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected error %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n}\n\nfunc TestFail(t *testing.T) {\n\n\tt.Run(\"explicit fail\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\terr0 := errors.New(\"x-ray error\")\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.Fail(err0)\n\t\t})\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected error %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"begin transaction fail\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\t\terr0 := errors.New(\"yankee error\")\n\n\t\tmock.ExpectBegin().WillReturnError(err0)\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\ttx.Fail(errors.New(\"should never happen\"))\n\t\t})\n\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected error %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"panic inside transaction\", func(t *testing.T) {\n\t\t// This test ensures that an arbitrary panic inside a transaction is not erroneously caught by us and instead\n\t\t// gets propagated back up as a panic.\n\t\tdb, mock := newMock(t)\n\t\terr0 := errors.New(\"zulu error\")\n\n\t\tmock.ExpectBegin()\n\n\t\tvar err error\n\t\tfunc() {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tif e, ok := r.(error); ok {\n\t\t\t\t\t\terr = e\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}()\n\t\t\tsx.Do(db, func(tx *sx.Tx) {\n\t\t\t\tpanic(err0)\n\t\t\t})\n\t\t}()\n\t\tif err != err0 {\n\t\t\tt.Errorf(\"expected panic %v, got %v\", err0, err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n\n\tt.Run(\"explicit nil fail\", func(t *testing.T) {\n\t\tdb, mock := newMock(t)\n\n\t\tmock.ExpectBegin()\n\t\tmock.ExpectRollback()\n\n\t\terr := sx.Do(db, func(tx *sx.Tx) {\n\t\t\t// This should roll back the transaction and return a nil error\n\t\t\ttx.Fail(nil)\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t}\n\n\t\tendMock(t, mock)\n\t})\n}\n"
  }
]