diff --git a/internal/data/catalog.go b/internal/data/catalog.go index 74f7a4e5..a6726cec 100644 --- a/internal/data/catalog.go +++ b/internal/data/catalog.go @@ -51,7 +51,7 @@ type Catalog interface { ReplaceTableFeature(ctx context.Context, name string, id string, feature Feature) error // CreateTableFeature creates a feature - CreateTableFeature(ctx context.Context, name string, feature Feature) error + CreateTableFeature(ctx context.Context, name string, feature Feature) (string, error) // DeleteTableFeature deletes a feature DeleteTableFeature(ctx context.Context, name string, id string) error diff --git a/internal/data/catalog_db.go b/internal/data/catalog_db.go index e3381cf7..267a9201 100644 --- a/internal/data/catalog_db.go +++ b/internal/data/catalog_db.go @@ -234,22 +234,22 @@ func (cat *catalogDB) TableFeature(ctx context.Context, name string, id string, return features[0], nil } -func (cat *catalogDB) CreateTableFeature(ctx context.Context, name string, feature Feature) error { +func (cat *catalogDB) CreateTableFeature(ctx context.Context, name string, feature Feature) (string, error) { tbl, err := cat.TableByName(name) if err != nil { - return err + return "", err } sql, argValues, err := sqlCreateFeature(tbl, feature) log.Debug("Create feature query: " + sql) - result, err := cat.dbconn.Exec(ctx, sql, argValues...) + row := cat.dbconn.QueryRow(ctx, sql, argValues...) + var featureId string + + err = row.Scan(&featureId) + if err != nil { - return err - } - rows := result.RowsAffected() - if rows != 1 { - return fmt.Errorf("expected to affect 1 row, affected %d", rows) + return "", err } - return nil + return featureId, err } func (cat *catalogDB) ReplaceTableFeature(ctx context.Context, name string, id string, feature Feature) error { diff --git a/internal/data/catalog_mock.go b/internal/data/catalog_mock.go index 076c7c83..435a1a8e 100644 --- a/internal/data/catalog_mock.go +++ b/internal/data/catalog_mock.go @@ -242,8 +242,8 @@ func (cat *CatalogMock) TableFeature(ctx context.Context, name string, id string return features[index].toJSON(propNames), nil } -func (cat *CatalogMock) CreateTableFeature(ctx context.Context, name string, feature Feature) error { - return nil +func (cat *CatalogMock) CreateTableFeature(ctx context.Context, name string, feature Feature) (string, error) { + return "", nil } func (cat *CatalogMock) ReplaceTableFeature(ctx context.Context, name string, id string, feature Feature) error { diff --git a/internal/data/db_sql.go b/internal/data/db_sql.go index 08cdf5bf..0fd8cebd 100644 --- a/internal/data/db_sql.go +++ b/internal/data/db_sql.go @@ -192,11 +192,31 @@ func sqlFeature(tbl *Table, param *QueryParam) string { return sql } -func getColumnValues(tbl *Table, feature Feature, includeOnlySetProperties bool) ([]string, []string, []interface{}) { +func getColumnValues(tbl *Table, feature Feature, includeOnlySetProperties bool) ([]string, []string, []interface{}, error) { var columnNames, columnIndex []string var columnValues []interface{} - var i = 2 + var i = 1 + + var geometryStr []byte + if feature.Geometry != nil { + var err error + geometryStr, err = json.Marshal(feature.Geometry) + if err != nil { + return nil, nil, nil, err + } + } + if feature.Geometry != nil { + columnNames = append(columnNames, tbl.GeometryColumn) + columnIndex = append(columnIndex, fmt.Sprintf("ST_Transform(ST_GeomFromGeoJSON($%v),%v)", i, tbl.Srid)) + columnValues = append(columnValues, geometryStr) + i++ + } else if !includeOnlySetProperties { + columnNames = append(columnNames, tbl.GeometryColumn) + columnIndex = append(columnIndex, fmt.Sprintf("$%v", i)) + columnValues = append(columnValues, feature.Geometry) + i++ + } for _, column := range tbl.Columns { val, ok := feature.Properties[column] @@ -208,14 +228,7 @@ func getColumnValues(tbl *Table, feature Feature, includeOnlySetProperties bool) } } - return columnNames, columnIndex, columnValues -} - -func buildGeometrySQL(tbl *Table) string { - if len(tbl.Columns) > 0 { - return fmt.Sprintf("ST_Transform(ST_GeomFromGeoJSON($1),%v)", tbl.Srid) - } - return fmt.Sprintf("ST_Transform(ST_GeomFromGeoJSON($1),%v)", tbl.Srid) + return columnNames, columnIndex, columnValues, nil } func buildUpdateSetClause(columnNames []string, columnIndex []string) string { @@ -227,45 +240,31 @@ func buildUpdateSetClause(columnNames []string, columnIndex []string) string { } func sqlCreateFeature(tbl *Table, feature Feature) (string, []interface{}, error) { - columnNames, columnIndex, columnValues := getColumnValues(tbl, feature, true) - - columnNamesStr := strings.Join(columnNames, ",") - columnIndexStr := strings.Join(columnIndex, ",") - geomSQL := buildGeometrySQL(tbl) - - sql := fmt.Sprintf("INSERT INTO \"%s\".\"%s\" (%s, %s) VALUES (%s, %v);", tbl.Schema, tbl.Table, columnNamesStr, tbl.GeometryColumn, columnIndexStr, geomSQL) - - var err error - argValues := make([]interface{}, len(columnValues)+1) - argValues[0], err = json.Marshal(feature.Geometry) + columnNames, columnIndex, columnValues, err := getColumnValues(tbl, feature, true) if err != nil { return "", nil, err } - copy(argValues[1:], columnValues) + columnNamesStr := strings.Join(columnNames, ",") + columnIndexStr := strings.Join(columnIndex, ",") + + sql := fmt.Sprintf("INSERT INTO \"%s\".\"%s\" (%s) VALUES (%s) RETURNING %s::varchar;", tbl.Schema, tbl.Table, columnNamesStr, columnIndexStr, tbl.IDColumn) - return sql, argValues, nil + return sql, columnValues, nil } func sqlReplaceFeature(tbl *Table, id string, feature Feature) (string, []interface{}, error) { - columnNames, columnIndex, columnValues := getColumnValues(tbl, feature, false) - - geomSQL := buildGeometrySQL(tbl) - setClause := buildUpdateSetClause(columnNames, columnIndex) - - sql := fmt.Sprintf("UPDATE \"%s\".\"%s\" SET %s=%v%s WHERE \"%v\" = $%v;", tbl.Schema, tbl.Table, tbl.GeometryColumn, geomSQL, setClause, tbl.IDColumn, len(tbl.Columns)+2) - - var err error - argValues := make([]interface{}, len(columnValues)+2) - argValues[0], err = json.Marshal(feature.Geometry) + columnNames, columnIndex, columnValues, err := getColumnValues(tbl, feature, true) if err != nil { return "", nil, err } + setClause := buildUpdateSetClause(columnNames, columnIndex) + + sql := fmt.Sprintf("UPDATE \"%s\".\"%s\" SET %s WHERE \"%v\" = $%v;", tbl.Schema, tbl.Table, setClause, tbl.IDColumn, len(columnNames)) - copy(argValues[1:], columnValues) - argValues[len(columnValues)+1] = id + columnValues = append(columnValues, (id)) - return sql, argValues, nil + return sql, columnValues, nil } func sqlDeleteFeature(tbl *Table, id string) (string, []interface{}) { diff --git a/internal/service/handler.go b/internal/service/handler.go index 896adc4f..cea8260b 100644 --- a/internal/service/handler.go +++ b/internal/service/handler.go @@ -293,17 +293,25 @@ func handleCollectionItems(w http.ResponseWriter, r *http.Request) *appError { } return nil case http.MethodPost: + if format != "json" { + return appErrorInternalFmt(nil, api.ErrMsgInvalidQuery) + } body, err := ioutil.ReadAll(r.Body) if err != nil { return appErrorInternalFmt(err, api.ErrMsgInvalidQuery) } - print(body) var feature data.Feature err = json.Unmarshal([]byte(body), &feature) if err != nil { return appErrorInternalFmt(err, api.ErrMsgInvalidQuery) } - catalogInstance.CreateTableFeature(ctx, name, feature) + var featureId string + featureId, err = catalogInstance.CreateTableFeature(ctx, name, feature) + if err != nil { + return appErrorInternalFmt(err, api.ErrMsgInvalidQuery) + } + urlBase := serveURLBase(r) + w.Header().Set("Location", urlBase+"collections/"+name+"/items/"+featureId+".json") w.WriteHeader(http.StatusCreated) return nil @@ -406,11 +414,13 @@ func handleItem(w http.ResponseWriter, r *http.Request) *appError { return appErrorInternalFmt(errQuery, api.ErrMsgInvalidQuery) } case http.MethodPut: + if format != "json" { + return appErrorInternalFmt(nil, api.ErrMsgInvalidQuery) + } body, err := ioutil.ReadAll(r.Body) if err != nil { return appErrorInternalFmt(err, api.ErrMsgInvalidQuery) } - print(body) var inputFeature data.Feature err = json.Unmarshal([]byte(body), &inputFeature) if err != nil {