github.com/PDOK/gokoala@v0.50.6/internal/ogc/features/datasources/geopackage/asserts.go (about)

     1  package geopackage
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/PDOK/gokoala/config"
     9  
    10  	"github.com/jmoiron/sqlx"
    11  )
    12  
    13  // assertIndexesExist asserts required indexes in the GeoPackage exists
    14  func assertIndexesExist(
    15  	configuredCollections config.GeoSpatialCollections,
    16  	featureTableByCollectionID map[string]*featureTable,
    17  	db *sqlx.DB, fidColumn string) error {
    18  
    19  	// index needs to contain these columns in the given order
    20  	defaultSpatialBtreeColumns := strings.Join([]string{fidColumn, "minx", "maxx", "miny", "maxy"}, ",")
    21  
    22  	for collID, table := range featureTableByCollectionID {
    23  		if table == nil {
    24  			return errors.New("given table can't be nil")
    25  		}
    26  		for _, coll := range configuredCollections {
    27  			if coll.ID == collID && coll.Features != nil {
    28  				spatialBtreeColumns := defaultSpatialBtreeColumns
    29  
    30  				// assert temporal columns are indexed if configured
    31  				if coll.Metadata != nil && coll.Metadata.TemporalProperties != nil {
    32  					temporalBtreeColumns := strings.Join([]string{coll.Metadata.TemporalProperties.StartDate, coll.Metadata.TemporalProperties.EndDate}, ",")
    33  					spatialBtreeColumns = strings.Join([]string{defaultSpatialBtreeColumns, coll.Metadata.TemporalProperties.StartDate, coll.Metadata.TemporalProperties.EndDate}, ",")
    34  					if err := assertIndexExists(table.TableName, db, temporalBtreeColumns, true); err != nil {
    35  						return err
    36  					}
    37  				}
    38  
    39  				// assert spatial b-tree index exists, this index substitutes the r-tree when querying large bounding boxes
    40  				// if temporal columns are configured, they must be included in this index as well
    41  				if err := assertIndexExists(table.TableName, db, spatialBtreeColumns, true); err != nil {
    42  					return err
    43  				}
    44  
    45  				// assert the column for each property filter is indexed.
    46  				for _, propertyFilter := range coll.Features.Filters.Properties {
    47  					if err := assertIndexExists(table.TableName, db, propertyFilter.Name, false); err != nil && *propertyFilter.IndexRequired {
    48  						return fmt.Errorf("%w. To disable this check set 'indexRequired' to 'false'", err)
    49  					}
    50  				}
    51  				break
    52  			}
    53  		}
    54  	}
    55  	return nil
    56  }
    57  
    58  func assertIndexExists(tableName string, db *sqlx.DB, columns string, prefixMatch bool) error {
    59  	query := fmt.Sprintf(`
    60  select group_concat(info.name) as indexed_columns
    61  from pragma_index_list('%s') as list,
    62       pragma_index_info(list.name) as info
    63  group by list.name`, tableName)
    64  
    65  	rows, err := db.Queryx(query)
    66  	if err != nil {
    67  		return fmt.Errorf("failed to read indexes from table '%s'", tableName)
    68  	}
    69  	exists := false
    70  	for rows.Next() {
    71  		var indexedColumns string
    72  		_ = rows.Scan(&indexedColumns)
    73  		if columns == indexedColumns {
    74  			exists = true // index on expected columns
    75  		} else if prefixMatch && strings.HasPrefix(indexedColumns, columns) {
    76  			exists = true // index with expected prefix columns
    77  		}
    78  	}
    79  	defer rows.Close()
    80  	if !exists {
    81  		return fmt.Errorf("missing required index: no index exists on column(s) '%s' in table '%s'",
    82  			columns, tableName)
    83  	}
    84  	return nil
    85  }