Add support for PostGIS Geometry columns

This is primarily an experiment. At this point I'm getting back the
Geometry as a GeoJSON blob, which is pretty massive progress, but I'm
still not able to manipulate the data directly the way I'd like.
This commit is contained in:
Eli Ribble 2026-05-06 20:36:10 +00:00
parent 2053415c76
commit 814ebddfa2
No known key found for this signature in database
7 changed files with 362 additions and 103 deletions

View file

@ -2,10 +2,6 @@
package jet
import (
"github.com/go-jet/jet/v2/internal/3rdparty/snaker"
)
// Column is common column interface for all types of columns.
type Column interface {
Name() string
@ -28,101 +24,3 @@ type ColumnExpression interface {
Expression
}
// ColumnExpressionImpl is base type for sql columns.
type ColumnExpressionImpl struct {
ExpressionInterfaceImpl
name string
tableName string
subQuery SelectTable
}
// NewColumnImpl creates new ColumnExpressionImpl
func NewColumnImpl(name string, tableName string, root ColumnExpression) *ColumnExpressionImpl {
newColumn := &ColumnExpressionImpl{
name: name,
tableName: tableName,
}
if root != nil {
newColumn.ExpressionInterfaceImpl.Root = root
} else {
newColumn.ExpressionInterfaceImpl.Root = newColumn
}
return newColumn
}
// Name returns name of the column
func (c *ColumnExpressionImpl) Name() string {
return c.name
}
// TableName returns column table name
func (c *ColumnExpressionImpl) TableName() string {
return c.tableName
}
func (c *ColumnExpressionImpl) setTableName(table string) {
c.tableName = table
}
func (c *ColumnExpressionImpl) setSubQuery(subQuery SelectTable) {
c.subQuery = subQuery
}
func (c *ColumnExpressionImpl) defaultAlias() string {
if c.tableName != "" {
return c.tableName + "." + c.name
}
return c.name
}
func (c *ColumnExpressionImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
if statement == SetStatementType {
// set Statement (UNION, EXCEPT ...) can reference only select projections in order by clause
out.WriteAlias(c.defaultAlias()) //always quote
return
}
c.serialize(statement, out)
}
func (c *ColumnExpressionImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
c.serialize(statement, out)
out.WriteString("AS")
out.WriteAlias(c.defaultAlias())
}
func (c *ColumnExpressionImpl) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
out.WriteJsonObjKey(snaker.SnakeToCamel(c.name, false))
c.Root.serializeForJsonValue(statement, out)
}
func (c *ColumnExpressionImpl) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
c.Root.serializeForJsonValue(statement, out)
out.WriteString("AS")
out.WriteAlias(snaker.SnakeToCamel(c.name, false))
}
func (c *ColumnExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if c.subQuery != nil {
out.WriteIdentifier(c.subQuery.Alias())
out.WriteByte('.')
out.WriteIdentifier(c.defaultAlias())
} else {
if c.tableName != "" && !contains(options, ShortName) {
out.WriteIdentifier(c.tableName)
out.WriteByte('.')
}
out.WriteIdentifier(c.name)
}
}

View file

@ -0,0 +1,109 @@
package jet
import (
"github.com/go-jet/jet/v2/internal/3rdparty/snaker"
)
// ColumnExpressionGeometryImpl is base type for sql columns.
type ColumnExpressionGeometryImpl struct {
ExpressionInterfaceImpl
name string
tableName string
subQuery SelectTable
}
// Name returns name of the column
func (c *ColumnExpressionGeometryImpl) Name() string {
return c.name
}
// TableName returns column table name
func (c *ColumnExpressionGeometryImpl) TableName() string {
return c.tableName
}
func (c *ColumnExpressionGeometryImpl) setTableName(table string) {
c.tableName = table
}
func (c *ColumnExpressionGeometryImpl) setSubQuery(subQuery SelectTable) {
c.subQuery = subQuery
}
func (c *ColumnExpressionGeometryImpl) defaultAlias() string {
if c.tableName != "" {
return c.tableName + "." + c.name
}
return c.name
}
func (c *ColumnExpressionGeometryImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
if statement == SetStatementType {
// set Statement (UNION, EXCEPT ...) can reference only select projections in order by clause
out.WriteAlias(c.defaultAlias()) //always quote
return
}
c.serialize(statement, out)
}
func (c *ColumnExpressionGeometryImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
c.serialize(statement, out)
out.WriteString("AS")
out.WriteAlias(c.defaultAlias())
}
func (c *ColumnExpressionGeometryImpl) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
out.WriteJsonObjKey(snaker.SnakeToCamel(c.name, false))
c.Root.serializeForJsonValue(statement, out)
}
func (c *ColumnExpressionGeometryImpl) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
c.Root.serializeForJsonValue(statement, out)
out.WriteString("AS")
out.WriteAlias(snaker.SnakeToCamel(c.name, false))
}
func (c *ColumnExpressionGeometryImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if c.subQuery != nil {
out.WriteString("ST_AsGeoJSON(")
out.WriteIdentifier(c.subQuery.Alias())
out.WriteByte('.')
out.WriteIdentifier(c.defaultAlias())
out.WriteString(")")
} else {
out.WriteString("ST_AsGeoJSON(")
if c.tableName != "" && !contains(options, ShortName) {
out.WriteIdentifier(c.tableName)
out.WriteByte('.')
}
out.WriteIdentifier(c.name)
out.WriteString(")")
}
}
func NewColumnGeometryImpl(name string, tableName string, root ColumnExpression) *ColumnExpressionGeometryImpl {
newColumn := &ColumnExpressionGeometryImpl{
name: name,
tableName: tableName,
}
if root != nil {
newColumn.ExpressionInterfaceImpl.Root = root
} else {
newColumn.ExpressionInterfaceImpl.Root = newColumn
}
return newColumn
}

View file

@ -0,0 +1,107 @@
// Modeling of columns
package jet
import (
"github.com/go-jet/jet/v2/internal/3rdparty/snaker"
)
// ColumnExpressionImpl is base type for sql columns.
type ColumnExpressionImpl struct {
ExpressionInterfaceImpl
name string
tableName string
subQuery SelectTable
}
// Name returns name of the column
func (c *ColumnExpressionImpl) Name() string {
return c.name
}
// TableName returns column table name
func (c *ColumnExpressionImpl) TableName() string {
return c.tableName
}
func (c *ColumnExpressionImpl) setTableName(table string) {
c.tableName = table
}
func (c *ColumnExpressionImpl) setSubQuery(subQuery SelectTable) {
c.subQuery = subQuery
}
func (c *ColumnExpressionImpl) defaultAlias() string {
if c.tableName != "" {
return c.tableName + "." + c.name
}
return c.name
}
func (c *ColumnExpressionImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
if statement == SetStatementType {
// set Statement (UNION, EXCEPT ...) can reference only select projections in order by clause
out.WriteAlias(c.defaultAlias()) //always quote
return
}
c.serialize(statement, out)
}
func (c *ColumnExpressionImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
c.serialize(statement, out)
out.WriteString("AS")
out.WriteAlias(c.defaultAlias())
}
func (c *ColumnExpressionImpl) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
out.WriteJsonObjKey(snaker.SnakeToCamel(c.name, false))
c.Root.serializeForJsonValue(statement, out)
}
func (c *ColumnExpressionImpl) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
c.Root.serializeForJsonValue(statement, out)
out.WriteString("AS")
out.WriteAlias(snaker.SnakeToCamel(c.name, false))
}
func (c *ColumnExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if c.subQuery != nil {
out.WriteIdentifier(c.subQuery.Alias())
out.WriteByte('.')
out.WriteIdentifier(c.defaultAlias())
} else {
if c.tableName != "" && !contains(options, ShortName) {
out.WriteIdentifier(c.tableName)
out.WriteByte('.')
}
out.WriteIdentifier(c.name)
}
}
// NewColumnImpl creates new ColumnExpressionImpl
func NewColumnImpl(name string, tableName string, root ColumnExpression) *ColumnExpressionImpl {
newColumn := &ColumnExpressionImpl{
name: name,
tableName: tableName,
}
if root != nil {
newColumn.ExpressionInterfaceImpl.Root = root
} else {
newColumn.ExpressionInterfaceImpl.Root = newColumn
}
return newColumn
}

View file

@ -573,3 +573,48 @@ func RangeColumn[T Expression](name string) ColumnRange[T] {
return rangeColumn
}
//------------------------------------------------------//
// ColumnGeometry is interface of PostGIS
type ColumnGeometry interface {
GeometryExpression
Column
From(subQuery SelectTable) ColumnGeometry
SET(timestampzExp GeometryExpression) ColumnAssigment
}
type geometryColumnImpl struct {
geometryInterfaceImpl
*ColumnExpressionGeometryImpl
}
func (i *geometryColumnImpl) fromImpl(subQuery SelectTable) Projection {
return i.From(subQuery)
}
func (i *geometryColumnImpl) From(subQuery SelectTable) ColumnGeometry {
newGeometryColumn := GeometryColumn(i.name)
newGeometryColumn.setTableName(i.tableName)
newGeometryColumn.setSubQuery(subQuery)
return newGeometryColumn
}
func (i *geometryColumnImpl) SET(geometryExp GeometryExpression) ColumnAssigment {
return columnAssigmentImpl{
column: i,
toAssign: geometryExp,
}
}
// GeometryColumn creates named timestamp with time zone column.
func GeometryColumn(name string) ColumnGeometry {
geometryColumn := &geometryColumnImpl{}
geometryColumn.geometryInterfaceImpl.root = geometryColumn
geometryColumn.ColumnExpressionGeometryImpl = NewColumnGeometryImpl(name, "", geometryColumn)
return geometryColumn
}

View file

@ -0,0 +1,94 @@
package jet
// GeometryExpression interface
type GeometryExpression interface {
Expression
EQ(rhs GeometryExpression) BoolExpression
NOT_EQ(rhs GeometryExpression) BoolExpression
IS_DISTINCT_FROM(rhs GeometryExpression) BoolExpression
IS_NOT_DISTINCT_FROM(rhs GeometryExpression) BoolExpression
LT(rhs GeometryExpression) BoolExpression
LT_EQ(rhs GeometryExpression) BoolExpression
GT(rhs GeometryExpression) BoolExpression
GT_EQ(rhs GeometryExpression) BoolExpression
BETWEEN(min, max GeometryExpression) BoolExpression
NOT_BETWEEN(min, max GeometryExpression) BoolExpression
ADD(rhs Interval) GeometryExpression
SUB(rhs Interval) GeometryExpression
}
type geometryInterfaceImpl struct {
root GeometryExpression
}
func (t *geometryInterfaceImpl) EQ(rhs GeometryExpression) BoolExpression {
return Eq(t.root, rhs)
}
func (t *geometryInterfaceImpl) NOT_EQ(rhs GeometryExpression) BoolExpression {
return NotEq(t.root, rhs)
}
func (t *geometryInterfaceImpl) IS_DISTINCT_FROM(rhs GeometryExpression) BoolExpression {
return IsDistinctFrom(t.root, rhs)
}
func (t *geometryInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs GeometryExpression) BoolExpression {
return IsNotDistinctFrom(t.root, rhs)
}
func (t *geometryInterfaceImpl) LT(rhs GeometryExpression) BoolExpression {
return Lt(t.root, rhs)
}
func (t *geometryInterfaceImpl) LT_EQ(rhs GeometryExpression) BoolExpression {
return LtEq(t.root, rhs)
}
func (t *geometryInterfaceImpl) GT(rhs GeometryExpression) BoolExpression {
return Gt(t.root, rhs)
}
func (t *geometryInterfaceImpl) GT_EQ(rhs GeometryExpression) BoolExpression {
return GtEq(t.root, rhs)
}
func (t *geometryInterfaceImpl) BETWEEN(min, max GeometryExpression) BoolExpression {
return NewBetweenOperatorExpression(t.root, min, max, false)
}
func (t *geometryInterfaceImpl) NOT_BETWEEN(min, max GeometryExpression) BoolExpression {
return NewBetweenOperatorExpression(t.root, min, max, true)
}
func (t *geometryInterfaceImpl) ADD(rhs Interval) GeometryExpression {
return GeometryExp(Add(t.root, rhs))
}
func (t *geometryInterfaceImpl) SUB(rhs Interval) GeometryExpression {
return GeometryExp(Sub(t.root, rhs))
}
//-------------------------------------------------
type geometryExpressionWrapper struct {
geometryInterfaceImpl
Expression
}
func newGeometryExpressionWrap(expression Expression) GeometryExpression {
geometryExpressionWrap := &geometryExpressionWrapper{Expression: expression}
geometryExpressionWrap.geometryInterfaceImpl.root = geometryExpressionWrap
expression.setRoot(geometryExpressionWrap)
return geometryExpressionWrap
}
// GeometryExp is timestamp with time zone expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as timestamp with time zone expression.
// Does not add sql cast to generated sql builder output.
func GeometryExp(expression Expression) GeometryExpression {
return newGeometryExpressionWrap(expression)
}