You can try https://github.com/xwb1989/sqlparser which is a MySQL parser and writer which is extracted from YouTube's vitessio/vitess <https://github.com/vitessio/vitess>. We have it forked to support SQL-92 standard https://github.com/CovenantSQL/sqlparser Also, we have some examples in our simple sharding implementation for CovenantSQL Example 1: we parse an UPDATE query and do some filtering and convert it to multiple UPDATEs on shard tables: https://github.com/CovenantSQL/CovenantSQL/blob/feature/shard/shard/update.go
Example 2: we parse a SELECT query and convert it to multiple SELECTs on shard tables, INSERT the results to a temp table, SELECT from the temp table: https://github.com/CovenantSQL/CovenantSQL/blob/feature/shard/shard/select.go On Sat, Dec 22, 2018 at 9:43 PM Victor Giordano <vitucho3...@gmail.com> wrote: > Hi Nick!. > Allow to me provide you with some insights about working with a sql DBMS > as persistence layer for an app. > > - *Json.Marshal/Unmarshal functions works like a charm* with columns > of type JSON. As you state once you have a table with one column of then > you can store anything on in, so the whatever data type you use in you > golang app it will "fit" into that column, with the "help" of the magical > functions of Json.Marsha, and you will "parse" it using Unmarshal > - Pros: No need to update table schema if the type value in your > application changes. > - Cons: Not a good idea if you are planning to lookup a row for a > value stored into that json. > - *Focus on modeling first, and on technology later*. I uses to work > with Hibernate in Java. I really appreciate the efforts of the communitiy > to try to make the life easier regarding object relational mappig, but in > the end, when you need to build your aggregations (those complex objects > that are maded of more granular ones), plain SQL provess to be more staight > foward and simple for me, as Eric says somettimes the "relationships are > much more important than objects" and it that cases the ORM may not provide > an straight solution. The real thing in object relational mapping, is not > only the ORM behind, is also, and more important (to me), how you model the > problem's domain entities. I mean, modelling your data base schema is like > putting your starting building blocks for an slyscraper, from there you > create on top of it, thus if you fail to properly understand a domain's > problem in its very granular terms (the true nature of things), you > probably will have a lot of additional code regardless of the abstractions > you use to communitcate with the db. > - *If you chosse to work with plain SQL,* i may have some good proved > recipes that work for me, that glady i will share with you. > - > - *(A) *I find the operation of scanning a row regardless of the > query that they may come, to "have a common behaviour", that i encapsulated > in some convenient abstractions. See the code below > - > - > > // Scans a single row from a given query > type RowScanFunc func(rows scaner) (model.Scanable, error) > // Scans multiples rows using a scanner function > func ScanMultiples(rows *sql.Rows, rowScanFunc RowScanFunc) > (interface{}, error) { > records:= []interface{} > for rows.Next() { > record, err := rowScanFunc(rows) > if record== nil { > return nil, err > } > records= append(records, record) > } > err := rows.Err() > if err != nil { > return nil, err > } > return objects , nil > } > > So, for example, you could use like this: > - > // sql persistence layer > CREATE TABLE bank_account > ( > id serial NOT NULL, > type text NOT NULL, > balance int NOT NULL, > CONSTRAINT bank_account_pkey PRIMARY KEY (id) > ) > > // golang app > type BankAccount struct { > Id int `json:"id"` > Type int `json:"balance"` > Balance int `json:"balance"` // btw: i don't > use floats for currency values, they can not be trust. > Details BankAccountDetails `json:"bankAccountDetails"` // asumme that > bank account details is another struct declared elsewhere > } > > // Gets all the accounts that correspondt to the given list of ids > func GetAllBankAccounts(accountsIds ...int) ([]interface{}, error) { > rows, err := Dba.Query(` > SELECT c.id, c.type, a.balance, c.details > FROM accounts c > WHERE c.id = ANY($1)`, pq.Array(accountsIds)) > if err != nil { > return nil, err > } > defer rows.Close() > return ScanMultiples(rows, scanBankAccount) > } > > // Scan a single row interpreting it as a "BankAccount" object type. > func scanBankAccount() (interface{}, error) { > var ba BankAccount > var details []byte > err := rows.Scan(&ba.Id, &ba.Type, &ba.Balance, &details) > if err != nil { > return nil, err > } > err = json.Unmarshal(details, &ba.Details) // <-- the magic we talk > about! nice! > if err != nil { > logger.Errorf("error unmarshalling bank account details(='%s'), error > was: '%v'", string(details), err) > } > return &b, nil > } > - *For dealing with transactions* i do find this snippet very very > useful (taken and adapted from: > https://stackoverflow.com/a/23502629/903998) > - > > type TransactionFunc = func(*sql.Tx) (interface{}, error) > > func ExecuteTransactionFunc(db *sql.DB, txFunc TransactionFunc) (result > interface{}, err error) { > tx, err := db.Begin() > if err != nil { > return > } > defer func() { > if p := recover(); p != nil { > tx.Rollback() > panic(p) // re-throw panic after Rollback > } else if err != nil { > tx.Rollback() // err is non-nil; don't change it > } else { > err = tx.Commit() // err is nil; if Commit returns error > update err > } > }() > result, err = txFunc(tx) > return > } > > And you could employ that code in scenarios like below: > - > - > - func TransferMoney (fromBankAccountId int, toBackAccountId int, > amount int) (interface{}, error) { > anonymousTransactionFunc := func(tx *sql.Tx) (interface{}, error) { // > a.k.a. "clousure" > return doTransferMoney(tx, session, centersRoles) > } > return ExecuteTransactionFunc(Db,anonymousTransactionFunc) // assuming > Db contains an actual connection to an underlying sql DB > } > > > func doTransferMoney(tx *sql.Tx fromBankAccountId int, toBackAccountId > int, amount int) (result interface{}, err error) { > err := withdrawMoneyFrom(tx, fromBankAccountId, amount) > if err != nil { > return > } > err = putMoneyInto(tx, toBankAccount, amount) > if err != nil { > return > } > result = struct{}{} // here you build your answer accordenly to the > system well defined bussiness rules > return > } > > > Well i guess that is a LOT for a start. Forgive me if i may overwhelm a > little bit with so much large response, but you ask for it! and i just try > to share with you what i have learned and worked for me!. > Hope it really helps, and, of course, i will be eager to read any feedback > regarding good practices working with SQL. > Greetings > *V* > > P.D: PLease forgive me if the code has errors, it was not tested, only for > describing examples and improving overall comprehension. > > > El jueves, 20 de diciembre de 2018, 16:19:50 (UTC-3), er...@tibco.com > escribió: >> >> >> >> On Thursday, December 20, 2018 at 7:23:22 AM UTC-8, Nick wrote: >>> >>> Hi all, >>> >>> I'm newish to Go, and I've been really enjoying using it. I'm still >>> getting to grips with the best practices of the language. >>> >>> Up until now, I've used MarshalJSON methods on complex struct types >>> to easily save the state of data, using Marshal() from >>> encoding/json. This has worked really well, and I have started to >>> get the simple power of interfaces with how easy it was to do. >>> >>> I've got to the stage that it makes more sense to use SQL than JSON >>> for my data now, and I was thinking it would make most sense to have >>> a similar process to read and write SQL, using methods on my struct >>> data types, which can then be called by functions that take an >>> interface implementing those methods. >>> >> >> What you're describing roughly fits into the category of an >> "object-relational mapping". Go has a number of solutions for that. One >> that pops to mind is GORM. >> >> >>> >>> I'm wondering if anybody else has done anything similar (I presume >>> so!), and could point me to examples of ways people have done this. >>> Any other thoughts on the best ways to organise my code to do this >>> cleanly and reusably would be very welcome indeed - as I say, I'm >>> new enough to Go that maybe I'm still thinking about this all wrong! >>> >> >> There are two directions I've seen recommended in the Go community. >> Either use ORM, or sidestep it completely, and just implement the SQL >> directly. >> >> I've found, for the places where I need to use SQL, that relationships >> are much more important than objects. So efficient interaction with the >> database ends up working best by simply directly writing SQL code, >> querying, and then scanning the results. ORMs would get in the way, because >> they would then require multiple ORM-level interactions to answer specific >> query. There's a little bit of repetitiveness in the non-ORM approach, but >> typically by the time I'm done, not so much that I'm concerned. >> >> Unlike with Java, where introducing a new class of object requires a new >> source file, with the tailored query approach I can create a structure that >> is directly fit for purpose, and it is just a few lines of additional code >> in an existing file. This means my database layer can easily get exactly >> the columns I need, from exactly the tables I need, and map that into >> exactly the structure that I need. >> >> A package like sqlx <https://github.com/jmoiron/sqlx> might even >> simplify your work even further. >> >> Eric. >> >> >>> >>> Thanks in advance, >>> >>> Nick >>> >> -- > You received this message because you are subscribed to the Google Groups > "golang-nuts" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to golang-nuts+unsubscr...@googlegroups.com. > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.