Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(go): Upgrade to Go 1.23 #240

Closed
wants to merge 8 commits into from
2 changes: 1 addition & 1 deletion .github/workflows/lint-go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:

- uses: actions/setup-go@v4
with:
go-version: '1.22'
go-version: '1.23'
cache: false

- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.22'
go-version: '1.23'

- name: Download
run: go mod download all
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ RUN npm install
RUN npm run build

####### Go build
FROM docker.io/golang:1.22-bookworm AS build-env
FROM docker.io/golang:1.23-bookworm AS build-env
WORKDIR /go/src/service
ADD . /go/src/service

Expand Down
28 changes: 28 additions & 0 deletions config/ogcapi_features.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ type CollectionEntryFeatures struct {
// +optional
Filters FeatureFilters `yaml:"filters,omitempty" json:"filters,omitempty"`

// Optional way to exclude feature properties and/or determine the ordering of properties in the response.
// +optional
FeatureProperties *FeatureProperties `yaml:",inline" json:",inline"`

// Downloads available for this collection through map sheets. Note that 'map sheets' refer to a map
// divided in rectangle areas that can be downloaded individually.
// +optional
Expand Down Expand Up @@ -304,6 +308,30 @@ type FeatureFilters struct {
// <placeholder>
}

// +kubebuilder:object:generate=true
type FeatureProperties struct {
// Properties/fields of features in this collection. This setting controls two things:
//
// A) allows one to exclude certain properties, when propertiesExcludeUnknown=true
// B) allows one sort the properties in the given order, when propertiesInSpecificOrder=true
//
// When not set all available properties are returned in API responses, in alphabetical order.
// +optional
Properties []string `yaml:"properties,omitempty" json:"properties,omitempty"`

// When true properties not listed under 'properties' are excluded from API responses. When false
// unlisted properties are also included in API responses.
// +optional
// +kubebuilder:default=false
PropertiesExcludeUnknown bool `yaml:"propertiesExcludeUnknown,omitempty" json:"propertiesExcludeUnknown,omitempty" default:"false"`

// When true properties are returned according to the ordering specified under 'properties'. When false
// properties are returned in alphabetical order.
// +optional
// +kubebuilder:default=false
PropertiesInSpecificOrder bool `yaml:"propertiesInSpecificOrder,omitempty" json:"propertiesInSpecificOrder,omitempty" default:"false"`
}

// +kubebuilder:object:generate=true
type MapSheetDownloads struct {
// Properties that provide the download details per map sheet. Note that 'map sheets' refer to a map
Expand Down
25 changes: 25 additions & 0 deletions config/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/PDOK/gokoala

go 1.22.5
go 1.23.1

require (
dario.cat/mergo v1.0.0
Expand Down
4 changes: 2 additions & 2 deletions hack/crd/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM docker.io/golang:1.22-bookworm AS build-env
FROM docker.io/golang:1.23-bookworm AS build-env
ADD hack/build-controller-gen.sh /build-controller-gen.sh
RUN /build-controller-gen.sh

FROM docker.io/golang:1.22-bookworm
FROM docker.io/golang:1.23-bookworm
COPY --from=build-env /bin/controller-gen /bin/controller-gen
ENTRYPOINT ["/bin/controller-gen"]
2 changes: 1 addition & 1 deletion hack/crd/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module crd

go 1.22.5
go 1.23.1

require (
github.com/PDOK/gokoala v0.0.0
Expand Down
1 change: 1 addition & 0 deletions hack/crd/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240816141633-0a40785b4f41/go.mod h1:DbzwytT4g/odXquuOCqroKvtxxldI4nb3nuesHF/Exo=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down
62 changes: 44 additions & 18 deletions internal/ogc/features/datasources/geopackage/geopackage.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"maps"
"os"
"path"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -79,6 +80,7 @@ type GeoPackage struct {
externalFidColumn string
featureTableByCollectionID map[string]*featureTable
propertyFiltersByCollectionID map[string]datasources.PropertyFiltersWithAllowedValues
propertiesByCollectionID map[string]*config.FeatureProperties
queryTimeout time.Duration
maxBBoxSizeToUseWithRTree int
}
Expand All @@ -88,6 +90,7 @@ func NewGeoPackage(collections config.GeoSpatialCollections, gpkgConfig config.G

g := &GeoPackage{}
g.preparedStmtCache = NewCache()
g.propertiesByCollectionID = cacheFeatureProperties(collections)
warmUp := false

switch {
Expand Down Expand Up @@ -151,7 +154,7 @@ func (g *GeoPackage) GetFeatureIDs(ctx context.Context, collection string, crite
queryCtx, cancel := context.WithTimeout(ctx, g.queryTimeout) // https://go.dev/doc/database/cancel-operations
defer cancel()

stmt, query, queryArgs, err := g.makeFeaturesQuery(queryCtx, table, true, criteria) //nolint:sqlclosecheck // prepared statement is cached, will be closed when evicted from cache
stmt, query, queryArgs, err := g.makeFeaturesQuery(queryCtx, g.propertiesByCollectionID[collection], table, true, criteria) //nolint:sqlclosecheck // prepared statement is cached, will be closed when evicted from cache
if err != nil {
return nil, domain.Cursors{}, fmt.Errorf("failed to create query '%s' error: %w", query, err)
}
Expand Down Expand Up @@ -204,7 +207,8 @@ func (g *GeoPackage) GetFeaturesByID(ctx context.Context, collection string, fea
}

fc := domain.FeatureCollection{}
fc.Features, _, err = domain.MapRowsToFeatures(rows, g.fidColumn, g.externalFidColumn, table.GeometryColumnName, mapGpkgGeometry, profile.MapRelationUsingProfile)
fc.Features, _, err = domain.MapRowsToFeatures(rows, g.fidColumn, g.externalFidColumn, table.GeometryColumnName,
g.propertiesByCollectionID[collection], mapGpkgGeometry, profile.MapRelationUsingProfile)
if err != nil {
return nil, err
}
Expand All @@ -221,7 +225,7 @@ func (g *GeoPackage) GetFeatures(ctx context.Context, collection string, criteri
queryCtx, cancel := context.WithTimeout(ctx, g.queryTimeout) // https://go.dev/doc/database/cancel-operations
defer cancel()

stmt, query, queryArgs, err := g.makeFeaturesQuery(queryCtx, table, false, criteria) //nolint:sqlclosecheck // prepared statement is cached, will be closed when evicted from cache
stmt, query, queryArgs, err := g.makeFeaturesQuery(queryCtx, g.propertiesByCollectionID[collection], table, false, criteria) //nolint:sqlclosecheck // prepared statement is cached, will be closed when evicted from cache
if err != nil {
return nil, domain.Cursors{}, fmt.Errorf("failed to create query '%s' error: %w", query, err)
}
Expand All @@ -237,7 +241,8 @@ func (g *GeoPackage) GetFeatures(ctx context.Context, collection string, criteri

var prevNext *domain.PrevNextFID
fc := domain.FeatureCollection{}
fc.Features, prevNext, err = domain.MapRowsToFeatures(rows, g.fidColumn, g.externalFidColumn, table.GeometryColumnName, mapGpkgGeometry, profile.MapRelationUsingProfile)
fc.Features, prevNext, err = domain.MapRowsToFeatures(rows, g.fidColumn, g.externalFidColumn, table.GeometryColumnName,
g.propertiesByCollectionID[collection], mapGpkgGeometry, profile.MapRelationUsingProfile)
if err != nil {
return nil, domain.Cursors{}, err
}
Expand Down Expand Up @@ -285,7 +290,8 @@ func (g *GeoPackage) GetFeature(ctx context.Context, collection string, featureI
return nil, queryCtx.Err()
}

features, _, err := domain.MapRowsToFeatures(rows, g.fidColumn, g.externalFidColumn, table.GeometryColumnName, mapGpkgGeometry, profile.MapRelationUsingProfile)
features, _, err := domain.MapRowsToFeatures(rows, g.fidColumn, g.externalFidColumn, table.GeometryColumnName,
g.propertiesByCollectionID[collection], mapGpkgGeometry, profile.MapRelationUsingProfile)
if err != nil {
return nil, err
}
Expand All @@ -309,24 +315,38 @@ func (g *GeoPackage) GetPropertyFiltersWithAllowedValues(collection string) data

// Build specific features queries based on the given options.
// Make sure to use SQL bind variables and return named params: https://jmoiron.github.io/sqlx/#namedParams
func (g *GeoPackage) makeFeaturesQuery(ctx context.Context, table *featureTable, onlyFIDs bool,
criteria datasources.FeaturesCriteria) (stmt *sqlx.NamedStmt, query string, queryArgs map[string]any, err error) {
func (g *GeoPackage) makeFeaturesQuery(ctx context.Context, propConfig *config.FeatureProperties, table *featureTable,
onlyFIDs bool, criteria datasources.FeaturesCriteria) (stmt *sqlx.NamedStmt, query string, queryArgs map[string]any, err error) {

selectClause := g.makeSelectClause(onlyFIDs, propConfig, table)
// make query
if criteria.Bbox != nil {
query, queryArgs, err = g.makeBboxQuery(table, onlyFIDs, criteria)
query, queryArgs, err = g.makeBboxQuery(table, selectClause, criteria)
if err != nil {
return
}
} else {
query, queryArgs = g.makeDefaultQuery(table, criteria)
query, queryArgs = g.makeDefaultQuery(table, selectClause, criteria)
}
// lookup prepared statement for given query, or create new one
stmt, err = g.preparedStmtCache.Lookup(ctx, g.backend.getDB(), query)
return
}

func (g *GeoPackage) makeDefaultQuery(table *featureTable, criteria datasources.FeaturesCriteria) (string, map[string]any) {
func (g *GeoPackage) makeSelectClause(onlyFIDs bool, propConfig *config.FeatureProperties, table *featureTable) string {
if onlyFIDs {
return "\"" + g.fidColumn + "\", prevfid, nextfid"
} else if propConfig != nil && propConfig.Properties != nil {
clause := "\"" + g.fidColumn + "\", prevfid, nextfid, \"" + table.GeometryColumnName + "\", " + strings.Join(propConfig.Properties, ",")
if !propConfig.PropertiesExcludeUnknown {
clause += ", *"
}
return clause
}
return "*"
}

func (g *GeoPackage) makeDefaultQuery(table *featureTable, selectClause string, criteria datasources.FeaturesCriteria) (string, map[string]any) {
pfClause, pfNamedParams := propertyFiltersToSQL(criteria.PropertyFilters)
temporalClause, temporalNamedParams := temporalCriteriaToSQL(criteria.TemporalCriteria)

Expand All @@ -336,8 +356,8 @@ with
prev as (select * from "%[1]s" where "%[2]s" < :fid %[3]s %[4]s order by %[2]s desc limit :limit),
nextprev as (select * from next union all select * from prev),
nextprevfeat as (select *, lag("%[2]s", :limit) over (order by %[2]s) as prevfid, lead("%[2]s", :limit) over (order by "%[2]s") as nextfid from nextprev)
select * from nextprevfeat where "%[2]s" >= :fid %[3]s %[4]s limit :limit
`, table.TableName, g.fidColumn, temporalClause, pfClause) // don't add user input here, use named params for user input!
select %[5]s from nextprevfeat where "%[2]s" >= :fid %[3]s %[4]s limit :limit
`, table.TableName, g.fidColumn, temporalClause, pfClause, selectClause) // don't add user input here, use named params for user input!

namedParams := map[string]any{
"fid": criteria.Cursor.FID,
Expand All @@ -348,12 +368,7 @@ select * from nextprevfeat where "%[2]s" >= :fid %[3]s %[4]s limit :limit
return defaultQuery, namedParams
}

func (g *GeoPackage) makeBboxQuery(table *featureTable, onlyFIDs bool, criteria datasources.FeaturesCriteria) (string, map[string]any, error) {
selectClause := "*"
if onlyFIDs {
selectClause = "\"" + g.fidColumn + "\", prevfid, nextfid"
}

func (g *GeoPackage) makeBboxQuery(table *featureTable, selectClause string, criteria datasources.FeaturesCriteria) (string, map[string]any, error) {
btreeIndexHint := fmt.Sprintf("indexed by \"%s_spatial_idx\"", table.TableName)

pfClause, pfNamedParams := propertyFiltersToSQL(criteria.PropertyFilters)
Expand Down Expand Up @@ -468,3 +483,14 @@ func temporalCriteriaToSQL(temporalCriteria datasources.TemporalCriteria) (sql s
}
return sql, namedParams
}

func cacheFeatureProperties(collections config.GeoSpatialCollections) map[string]*config.FeatureProperties {
result := make(map[string]*config.FeatureProperties)
for _, collection := range collections {
if collection.Features == nil {
continue
}
result[collection.ID] = collection.Features.FeatureProperties
}
return result
}
Loading
Loading