Skip to content
This repository has been archived by the owner on Aug 18, 2020. It is now read-only.

Commit

Permalink
Merge pull request #68 from dadleyy/psql-support
Browse files Browse the repository at this point in the history
postgres support
  • Loading branch information
dadleyy authored Nov 14, 2017
2 parents 32a14c5 + f43ecb5 commit b494fd0
Show file tree
Hide file tree
Showing 17 changed files with 865 additions and 264 deletions.
38 changes: 21 additions & 17 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
dist: trusty
language: go
services:
- postgresql
go:
- 1.8
- 1.9
- 1.8
- 1.9
before_script:
- psql -c 'create database marlow_test;' -U postgres
install:
- go get -v github.com/mattn/go-sqlite3
- go install -v -x github.com/mattn/go-sqlite3
- go get -v github.com/mattn/go-sqlite3
- go install -v -x github.com/mattn/go-sqlite3
script:
- make
- make test
- make test-example
- go run marlowc/main.go -input examples/library/models -stdout=true
- make example
- pushd ./examples/library && ./library && popd
- make
- make test
- make test-example
- go run marlowc/main.go -input examples/library/models -stdout=true
- make example
- pushd ./examples/library && ./library && popd
after_success:
- bash <(curl -s https://codecov.io/bash) -f ./coverage.txt
- bash <(curl -s https://codecov.io/bash) -f ./coverage.txt
before_deploy:
- mkdir -p ./dist/s3-commit/$TRAVIS_COMMIT
- mkdir -p ./dist/s3-latest/latest
- cp ./dist/coverage/library.coverage.txt ./dist/s3-commit/$TRAVIS_COMMIT
- cp ./dist/coverage/library.coverage.html ./dist/s3-commit/$TRAVIS_COMMIT
- cp ./dist/coverage/library.coverage.txt ./dist/s3-latest/latest
- cp ./dist/coverage/library.coverage.html ./dist/s3-latest/latest
- mkdir -p ./dist/s3-commit/$TRAVIS_COMMIT
- mkdir -p ./dist/s3-latest/latest
- cp ./dist/coverage/library.coverage.txt ./dist/s3-commit/$TRAVIS_COMMIT
- cp ./dist/coverage/library.coverage.html ./dist/s3-commit/$TRAVIS_COMMIT
- cp ./dist/coverage/library.coverage.txt ./dist/s3-latest/latest
- cp ./dist/coverage/library.coverage.html ./dist/s3-latest/latest
env:
global:
- secure: wJZxvUVrF+CNYQihaZ1ZLloaGN8/7GjZJ7vMUyXQmBwIwMj/bOCxGHBNglX8+o7XqJTVkGSIue0Vk0wic+fy1we8rTtUAfB7RdUyK0EBbOt0GBuziuk03V/cFOW7JodKC/Y3VAiLtOCgpw51KtT2ihg34LjVU3fSRsqIZdg7sskR537rfaZpVlyGVj6ZpD15Tiit4lFbBO9XvjjWi04sS9Q6+z3xMp6kpKfIa5vH1htQOXcsmgwWbT5VcBMsqRW4oDKolt9LbJ1MSrKEmW6/v+mAxCtoyWFPnuqU6/EtQjoTv8S5yu4MAwd6h1N3gS9jvWSW6Vfw8kjzO31fK8ZvMaGqbvERFl9jkHM6WutkklXDEX4YB+vGpxTmP+YjMGXphe2Sw4C9ILBqo49Co7Dz5j16TLK7DX/egpZsKrQcTE1nFZZEzmHHSPSHsn7PXgrcrDNHxtrNsw0sMgAijHRlCBI2w8GMQPIVaw79gpwp1oP21gY6YtIpq0B7SZPcwtTo9N0JxyZ6ZKW0bZIHVqA+R5pB29zvXdjA7JXnKAp4GVOGObxIoclw0csvQmDHxLfd81be0UNB2gTPzBC1CyiGom5lMSqbXIhB7PQF/LcwCP/FUGmnIcXdg3FfMPbdWuYQq9uic1e/s61enJA+cfk7Vxp/ZU2fPH+AKnlliu6wlBw=
Expand Down
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,12 @@ $(VENDOR_DIR):
$(GLIDE) install

example: $(LIBRARY_EXAMPLE_SRC) $(LIBRARY_EXAMPLE_MAIN) $(EXE)
$(GO) get -v github.com/lib/pq
$(GO) get -v github.com/mattn/go-sqlite3
$(GO) install -v -x github.com/mattn/go-sqlite3
$(GO) get -u github.com/jteeuwen/go-bindata/...
$(EXE) -input=$(LIBRARY_EXAMPLE_MODEL_DIR)
$(BINDATA) -o $(LIBRARY_DATA_DIR)/schema.go -pkg data -prefix $(LIBRARY_EXAMPLE_DIR) $(LIBRARY_DATA_DIR)/schema.sql
$(BINDATA) -o $(LIBRARY_DATA_DIR)/schema.go -pkg data -prefix $(LIBRARY_EXAMPLE_DIR) $(LIBRARY_DATA_DIR)/*.sql
$(COMPILE) $(BUILD_FLAGS) -o $(LIBRARY_EXAMPLE_EXE) $(LIBRARY_EXAMPLE_MAIN)

test-example: example
Expand All @@ -102,4 +103,5 @@ clean: clean-example
rm -rf $(LINT_RESULT)
rm -rf $(VENDOR_DIR)
rm -rf $(EXE)
rm -rf $(LIBRARY_EXAMPLE_DIR)/data/schema.go
rm -rf $(LIBRARY_DATA_DIR)/schema.go
rm -rf $(LIBRARY_DATA_DIR)/genres.go
93 changes: 70 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ several open source projects in the golang ecosystem who's goal is exactly that;
name a [few][awesome-go]. Marlow differs from these other projects in its philosophy; rather than attempt to provide an
eloquent orm for your project at runtime, it generates a tailored solution at compile time.

### Useage
### Usage

At its core, marlow simply reads a package's [field tags] and generates valid [golang] code. The `marlowc` executable
can be installed & used directly via:
Expand Down Expand Up @@ -61,43 +61,55 @@ distinguish them (useful if a project is using [`make`] to manage the build pipe
the generated files **are not committed to your project's revision control**; the source should *always* be generated
immediately before the rest of the package's source code is compiled.

### Field Tag Configuration
### Generated Code &amp; Field Tag Configuration

The compiler parses the `marlow` field tag value using the `net/url` package's [`parseQuery`] function. This means that
each configuration option supported by marlow would end up in delimited by the ampersand (`&`) character where the key
and value are separated by an equal sign (`=`). For example, a user record may look like:

```go
package Model
package models

type User struct {
table string `marlow:"tableName=users"`
ID uint `marlow:"column=id"`
Name string `marlow:"column=name"`
Email string `marlow:"column=name"`
Email string `marlow:"column=email"`
}
```

In this example, marlow would create golang code that would look (not exactly) like this:
In this example, marlow would create the following `UserStore` interface in the `models` package:

```go
func (s *UserStore) FindUsers(query *UserQuery) ([]*User, error) {
out := make([]*User, 0)
// ...
_rows, e := s.DB.Query(_generatedSQL) // e.g "SELECT id, name FROM users ..."
// ...

for _rows.Next() {
var _u User

if e := _rows.Scan(&u.Id, &u.Name); e != nil {
return e
}
type UserStore interface {
UpdateUserName(string, *UserBlueprint) (int64, error, string)
FindUsers(*UserBlueprint) ([]*User, error)
CountUsers(*UserBlueprint) (int, error)
SelectIDs(*UserBlueprint) ([]uint, error)
SelectEmails(*UserBlueprint) ([]string, error)
CreateUsers(...User) (int64, error)
UpdateUserEmail(string, *UserBlueprint) (int64, error, string)
UpdateUserID(uint, *UserBlueprint) (int64, error, string)
DeleteUsers(*UserBlueprint) (int64, error)
SelectNames(*UserBlueprint) ([]string, error)
}
```

out = append(out, &_u)
}
For every store that is generated, marlow will create a "blueprint" struct that defines a set of fields to be used for
querying against the database. In this example, the `UserBlueprint` generated for the store above would look like:

// ...
```go
type UserBlueprint struct {
IDRange []uint
ID []uint
NameLike []string
Name []string
EmailLike []string
Email []string
Limit int
Offset int
OrderBy string
OrderDirection string
}
```

Expand All @@ -109,16 +121,41 @@ overrides for default marlow assumptions about the table.
| Option | Description |
| :--- | :--- |
| `tableName` | The name of the table (marlow will assume a lowercased &amp; pluralized version of the struct name). |
| `defaultLimit` | When using the queryable feature, this will be the default maximum number of records to load. |
| `dialect` | Specifying the `dialect` option determines the syntax used by marlow during db queries. See [supported-drivers](#supported-drivers) for more info. |
| `primaryKey` | Some dialects require that marlow knows of the table's primary key during transactions. If provided, this value will be used as the _column name_ by marlow. |
| `storeName` | The name of the store type that will be generated, defaults to `%sStore`, where `%s` is the name of the struct. |
| `blueprintRangeFieldSuffix` | A string that is added to numerical blueprint fields for range selections. |
| `blueprintLikeFieldSuffix` | A string that is added to string/text blueprint fields for like selections. |
| `blueprintName` | The name of the blueprint type that will be generated, defaults to `%sBlueprint`, where `%s` is the name of the struct. |
| `defaultLimit` | When using the queryable feature, this will be the default maximum number of records to load. |
| `blueprintRangeFieldSuffix` | A string that is added to numerical blueprint fields for range selections. Defults to `%sRange` where `%s` is the name of the field (e.g: `AuthorIDRange`). |
| `blueprintLikeFieldSuffix` | A string that is added to string/text blueprint fields for like selections. Defaults to `%sLike` where `%s` is the name of the field (e.g: `FirstNameLike`). |

**All other fields**

For every other field found on the source `struct`, marlow will use the following field tag values:

| Option | Description |
| :--- | :--- |
| `column` | This is the column that any raw sql generated will target when scanning/selecting/querying this field. |
| `autoIncrement` | If `true`, this flag will prevent marlow from generating sql during creation that would attempt to insert the value of the field for the column. |

#### Generated Coverage & Documentation

While everyone's generated marlow code will likely be unique, the [`examples/library`] application includes a
comprehensive test suite that demonstrates the features of marlow using 3 models - [`Author`], [`Book`] and [`Genre`].

Although generated documentation is not currently available (see [`GH-67`]), the html coverage reports for tagged
builds can be found [here][generated-coverage.url]. There you will be able to see exactly what code is generated given
the models in the example app.

#### Supported Drivers

The follow is a list of officially support [`driver.Driver`] implementations supported by the generated marlow code. To request an additional driver, feel free to open up an [issue][issues].

| Driver Name | `dialect` Value |
| :--- | :--- |
| [`github.com/lib/pq`] | `postgres` |
| [`github.com/mattn/go-sqlite3`] | none or `sql` |
| [`github.com/go-sql-driver/mysql`] | none or `sql` |


----
Expand All @@ -140,6 +177,7 @@ generated coverage badge provided by [gendry]
[crud]: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete
[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
[Rails]: http://rubyonrails.org
[issues]: https://github.com/dadleyy/marlow/issues
[ActiveRecord]: http://guides.rubyonrails.org/active_record_basics.html
[ORM]: https://en.wikipedia.org/wiki/Object-relational_mapping
[mysql]: https://www.mysql.com
Expand All @@ -165,3 +203,12 @@ generated coverage badge provided by [gendry]
[generated-coverage.img]: http://gendry.sizethree.cc/coverage/dadleyy/marlow.svg
[generated-coverage.url]: http://coverage.marlow.sizethree.cc.s3.amazonaws.com/latest/library.coverage.html
[gendry]: https://bitbucket.org/dadleyy/gendry
[`github.com/lib/pq`]: https://github.com/lib/pq
[`github.com/mattn/go-sqlite3`]: https://github.com/mattn/go-sqlite3
[`github.com/go-sql-driver/mysql`]: https://github.com/go-sql-driver/mysql
[`driver.Driver`]: https://golang.org/pkg/database/sql/driver/#Driver
[`examples/library`]: https://github.com/dadleyy/marlow/tree/master/examples/library
[`Author`]: https://github.com/dadleyy/marlow/blob/master/examples/library/models/author_test.go
[`Book`]: https://github.com/dadleyy/marlow/blob/master/examples/library/models/book_test.go
[`Genre`]: https://github.com/dadleyy/marlow/blob/master/examples/library/models/genre_test.go
[`GH-67`]: https://github.com/dadleyy/marlow/issues/67
7 changes: 7 additions & 0 deletions examples/library/data/genres.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
drop table if exists genres;

create table genres (
id SERIAL,
name TEXT,
parent_id INTEGER
);
28 changes: 26 additions & 2 deletions examples/library/models/author_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,14 @@ func Test_Author(t *testing.T) {
g.Assert(len(authors)).Equal(2)
})

g.It("allows the consumer to search for authors by ID range", func() {
authors, e := store.FindAuthors(&AuthorBlueprint{
IDRange: []int{1, 4},
})
g.Assert(e).Equal(nil)
g.Assert(len(authors)).Equal(2)
})

g.It("correctly serializes null/not null values into a sql.NullInt64 field", func() {
authors, e := store.FindAuthors(&AuthorBlueprint{
ID: []int{1337, 1338},
Expand Down Expand Up @@ -336,13 +344,29 @@ func Test_Author(t *testing.T) {
g.Assert(s).Equal(0)
})

g.It("returns the number of authors created", func() {
s, e := store.CreateAuthors(Author{Name: "Danny"}, Author{Name: "Amelia"})
g.It("returns the id of the latest author created", func() {
s, e := store.CreateAuthors([]Author{
{Name: "Danny", ReaderRating: 25.00},
{Name: "Amelia", ReaderRating: 99.99},
}...)
g.Assert(e).Equal(nil)

found, e := store.FindAuthors(&AuthorBlueprint{ID: []int{int(s)}})
g.Assert(e).Equal(nil)
g.Assert(len(found)).Equal(1)
g.Assert(found[0].Name).Equal("Amelia")

badReaderCount, e := store.CountAuthors(&AuthorBlueprint{
ReaderRating: []float64{25.00},
})
g.Assert(e).Equal(nil)
g.Assert(badReaderCount).Equal(1)

goodReaderCount, e := store.CountAuthors(&AuthorBlueprint{
ReaderRating: []float64{99.99},
})
g.Assert(e).Equal(nil)
g.Assert(goodReaderCount).Equal(1)
})
})

Expand Down
11 changes: 11 additions & 0 deletions examples/library/models/genre.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package models

import "database/sql"

// Genre records are used to group and describe a types of books.
type Genre struct {
table bool `marlow:"tableName=genres&dialect=postgres&primaryKey=id"`
ID uint `marlow:"column=id&autoIncrement=true"`
Name string `marlow:"column=name"`
ParentID sql.NullInt64 `marlow:"column=parent_id"`
}
Loading

0 comments on commit b494fd0

Please sign in to comment.