Skip to content

Commit

Permalink
Merge pull request #3 from FreeLeh/improve-stability-simplify-features
Browse files Browse the repository at this point in the history
Improve stability and simplify features
  • Loading branch information
fatanugraha authored Aug 22, 2022
2 parents 2a60d7a + 041a0c8 commit f9f89e5
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 296 deletions.
54 changes: 41 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,10 @@ type Person struct {

// Inserts a bunch of rows.
// Note that the here matters, and it should follow the GoogleSheetRowStoreConfig.Columns settings.
_ = store.RawInsert(
[]interface{}{"name1", 10},
[]interface{}{"name2", 11},
[]interface{}{"name3", 12},
_ = store.Insert(
Person{Name: "name1", Age: 10},
Person{Name: "name2", Age: 11},
Person{Name: "name3", Age: 12},
).Exec(context.Background())

// Updates the name column for rows with age = 10
Expand Down Expand Up @@ -389,25 +389,53 @@ Examples:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
err := store.Select(&result).Where("name = ? AND age = ?", "bob", 12).Exec(ctx)
```

### `RawInsert(rows ...[]interface{}) *googleSheetRawInsertStmt`

- `RawInsert` returns a statement to perform the actual insert operation.
- The `rows` argument is a slice of an `interface{}` slice.
- The ordering of the values inside each slice of `interface{}` matters as it is the ordering that this library will use when inserting into the Google Sheet.
### `Count() *googleSheetCountStmt`

- `Count` returns a statement to perform the actual row counting operation.

#### `googleSheetCountStmt`

##### `Where(condition string, args ...interface{}) *googleSheetCountStmt`

This works exactly the same as the `googleSheetSelectStmt.Where` function. You can refer to the above section for more details.

##### `Exec(ctx context.Context) (uint64, error)`

- This function will actually execute the `SELECT` statement and then counting the number of rows matching the criteria.
- The number of rows is returned as a `uint64` return value.
- There is only one API call involved in this function.

### `Insert(rows ...interface{}) *googleSheetInsertStmt`

> This function is called `RawInsert` because the library is not really concerned with how the values in each row is formed.
> There is also no type checking involved.
> In the future, we are thinking of adding an `Insert` function that will provide a simple type checking mechanism.
- `Insert` returns a statement to perform the actual insert operation.
- Each element of the `rows` argument must be a struct or a pointer to a struct. Providing data types will result in an error during `Exec()`.
- Please note that the struct field name will be used as the column name (case-sensitive). If you want to change the mapping, you can add the struct field tag `db:"<col_name>"`.

#### `googleSheetRawInsertStmt`
#### `googleSheetInsertStmt`

##### `Exec(ctx context.Context) error`

- This function will actually execute the `INSERT` statement.
- This works by appending new rows into Google Sheets.
- There is only one API call involved in this function.

Examples:

```go
// Without the `db` struct tag, the column name used will be "Name" and "Age".
type Person struct {
Name string `db:"name"`
Age int `db:"age"`
}

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
err := store.Insert(
Person{Name: "name1", Age: 11},
Person{Name: "name2", Age: 12},
).Exec(ctx)
```

### `Update(colToValue map[string]interface{}) *googleSheetUpdateStmt`

- `Update` returns a statement to perform the actual update operation.
Expand Down
2 changes: 1 addition & 1 deletion internal/google/fixtures/service_account.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/service-something%40scripts-327408.iam.gserviceaccount.com"
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509"
}
29 changes: 26 additions & 3 deletions models.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"regexp"
"strconv"

"github.com/FreeLeh/GoFreeLeh/internal/google/sheets"
)
Expand Down Expand Up @@ -36,12 +37,14 @@ const (
kvFindKeyA1RangeQueryTemplate = "=MATCH(\"%s\", %s, 0)"

rowGetIndicesQueryTemplate = "=JOIN(\",\", ARRAYFORMULA(QUERY({%s, ROW(%s)}, \"%s\")))"
rwoCountQueryTemplate = "=COUNT(QUERY(%s, \"%s\"))"
rowUpdateModifyWhereNonEmptyTemplate = "%s IS NOT NULL AND %s"
rowUpdateModifyWhereEmptyTemplate = "%s IS NOT NULL"

naValue = "#N/A"
errorValue = "#ERROR!"
rowTsCol = "_ts"
naValue = "#N/A"
errorValue = "#ERROR!"
rowIdxCol = "_rid"
rowIdxFormula = "=ROW()"
)

var (
Expand All @@ -51,6 +54,8 @@ var (
defaultRowFullTableRange = "A2:" + generateColumnName(maxColumn-1)
rowDeleteRangeTemplate = "A%d:" + generateColumnName(maxColumn-1) + "%d"

lastColIdxName = "Col" + strconv.FormatInt(int64(maxColumn+1), 10)

googleSheetSelectStmtStringKeyword = regexp.MustCompile("^(date|datetime|timeofday)")
)

Expand All @@ -77,6 +82,24 @@ type colIdx struct {
idx int
}

type colsMapping map[string]colIdx

func (m colsMapping) NameMap() map[string]string {
result := make(map[string]string, 0)
for col, val := range m {
result[col] = val.name
}
return result
}

func (m colsMapping) ColIdxNameMap() map[string]string {
result := make(map[string]string, 0)
for col, val := range m {
result[col] = "Col" + strconv.FormatInt(int64(val.idx+1), 10)
}
return result
}

type ColumnOrderBy struct {
Column string
OrderBy OrderBy
Expand Down
21 changes: 10 additions & 11 deletions row.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,14 @@ type GoogleSheetRowStore struct {
sheetName string
scratchpadSheetName string
scratchpadLocation sheets.A1Range
colsMapping map[string]colIdx
colsMapping colsMapping
config GoogleSheetRowStoreConfig
}

func (s *GoogleSheetRowStore) Select(output interface{}, columns ...string) *googleSheetSelectStmt {
return newGoogleSheetSelectStmt(s, output, columns)
}

// RawInsert inserts all the values in `rows` into the table.
// Note that each value in the `interface{}` is going to be JSON marshalled.
func (s *GoogleSheetRowStore) RawInsert(rows ...[]interface{}) *googleSheetRawInsertStmt {
for i := range rows {
rows[i] = append(rows[i], currentTimeMs())
}
return newGoogleSheetRawInsertStmt(s, rows)
}

// Insert will try to infer what is the type of each row and perform certain logic based on the type.
// For example, a struct will be converted into a map[string]interface{} and then into []interface{} (following the
// column mapping ordering).
Expand All @@ -69,6 +60,10 @@ func (s *GoogleSheetRowStore) Delete() *googleSheetDeleteStmt {
return newGoogleSheetDeleteStmt(s)
}

func (s *GoogleSheetRowStore) Count() *googleSheetCountStmt {
return newGoogleSheetCountStmt(s)
}

func (s *GoogleSheetRowStore) Close(ctx context.Context) error {
_, err := s.wrapper.Clear(ctx, s.spreadsheetID, []string{s.scratchpadLocation.Original})
return err
Expand Down Expand Up @@ -149,6 +144,10 @@ func NewGoogleSheetRowStore(
// Currently, we use this for detecting which rows are really empty for UPDATE without WHERE clause.
// Otherwise, it will always update all rows (instead of the non-empty rows only).
func injectTimestampCol(config GoogleSheetRowStoreConfig) GoogleSheetRowStoreConfig {
config.Columns = append(config.Columns, rowTsCol)
newCols := make([]string, 0, len(config.Columns)+1)
newCols = append(newCols, rowIdxCol)
newCols = append(newCols, config.Columns...)
config.Columns = newCols

return config
}
17 changes: 14 additions & 3 deletions row_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ func TestGoogleSheetRowStore_Integration(t *testing.T) {
assert.Nil(t, err)
assert.Empty(t, out)

err = db.RawInsert(
[]interface{}{"name1", 10, "1-1-1999"},
[]interface{}{"name2", 11, "1-1-2000"},
err = db.Insert(
testPerson{"name1", 10, "1-1-1999"},
testPerson{"name2", 11, "1-1-2000"},
).Exec(context.Background())
assert.Nil(t, err)

Expand Down Expand Up @@ -80,6 +80,17 @@ func TestGoogleSheetRowStore_Integration(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, expected, out)

count, err := db.Count().
Where("name = ? OR name = ?", "name2", "name3").
Exec(context.Background())
assert.Nil(t, err)
assert.Equal(t, uint64(2), count)

err = db.Delete().Where("name = ?", "name4").Exec(context.Background())
assert.Nil(t, err)
}

func TestInjectTimestampCol(t *testing.T) {
result := injectTimestampCol(GoogleSheetRowStoreConfig{Columns: []string{"col1", "col2"}})
assert.Equal(t, GoogleSheetRowStoreConfig{Columns: []string{rowIdxCol, "col1", "col2"}}, result)
}
Loading

0 comments on commit f9f89e5

Please sign in to comment.