Save address IDs when doing pool geocoding

This commit is contained in:
Eli Ribble 2026-04-15 20:29:42 +00:00
parent 6a8ae6d81a
commit ac27c60e0c
No known key found for this signature in database
8 changed files with 447 additions and 52 deletions

View file

@ -222,6 +222,15 @@ var FileuploadPools = Table[
Generated: false,
AutoIncr: false,
},
AddressID: column{
Name: "address_id",
DBType: "integer",
Default: "NULL",
Comment: "",
Nullable: true,
Generated: false,
AutoIncr: false,
},
},
Indexes: fileuploadPoolIndexes{
PoolPkey: index{
@ -248,6 +257,15 @@ var FileuploadPools = Table[
Comment: "",
},
ForeignKeys: fileuploadPoolForeignKeys{
FileuploadPoolPoolAddressIDFkey: foreignKey{
constraint: constraint{
Name: "fileupload.pool.pool_address_id_fkey",
Columns: []string{"address_id"},
Comment: "",
},
ForeignTable: "address",
ForeignColumns: []string{"id"},
},
FileuploadPoolPoolCreatorIDFkey: foreignKey{
constraint: constraint{
Name: "fileupload.pool.pool_creator_id_fkey",
@ -313,11 +331,12 @@ type fileuploadPoolColumns struct {
AddressLocality column
AddressRegion column
Condition column
AddressID column
}
func (c fileuploadPoolColumns) AsSlice() []column {
return []column{
c.AddressPostalCode, c.AddressStreet, c.Committed, c.Created, c.CreatorID, c.CSVFile, c.Deleted, c.Geom, c.H3cell, c.ID, c.IsInDistrict, c.IsNew, c.Notes, c.PropertyOwnerName, c.PropertyOwnerPhoneE164, c.ResidentOwned, c.ResidentPhoneE164, c.LineNumber, c.Tags, c.AddressNumber, c.AddressLocality, c.AddressRegion, c.Condition,
c.AddressPostalCode, c.AddressStreet, c.Committed, c.Created, c.CreatorID, c.CSVFile, c.Deleted, c.Geom, c.H3cell, c.ID, c.IsInDistrict, c.IsNew, c.Notes, c.PropertyOwnerName, c.PropertyOwnerPhoneE164, c.ResidentOwned, c.ResidentPhoneE164, c.LineNumber, c.Tags, c.AddressNumber, c.AddressLocality, c.AddressRegion, c.Condition, c.AddressID,
}
}
@ -332,6 +351,7 @@ func (i fileuploadPoolIndexes) AsSlice() []index {
}
type fileuploadPoolForeignKeys struct {
FileuploadPoolPoolAddressIDFkey foreignKey
FileuploadPoolPoolCreatorIDFkey foreignKey
FileuploadPoolPoolCSVFileFkey foreignKey
FileuploadPoolPoolPropertyOwnerPhoneE164Fkey foreignKey
@ -340,7 +360,7 @@ type fileuploadPoolForeignKeys struct {
func (f fileuploadPoolForeignKeys) AsSlice() []foreignKey {
return []foreignKey{
f.FileuploadPoolPoolCreatorIDFkey, f.FileuploadPoolPoolCSVFileFkey, f.FileuploadPoolPoolPropertyOwnerPhoneE164Fkey, f.FileuploadPoolPoolResidentPhoneE164Fkey,
f.FileuploadPoolPoolAddressIDFkey, f.FileuploadPoolPoolCreatorIDFkey, f.FileuploadPoolPoolCSVFileFkey, f.FileuploadPoolPoolPropertyOwnerPhoneE164Fkey, f.FileuploadPoolPoolResidentPhoneE164Fkey,
}
}

View file

@ -0,0 +1,4 @@
-- +goose Up
ALTER TABLE fileupload.pool ADD COLUMN address_id INTEGER REFERENCES address(id);
-- +goose Down
ALTER TABLE fileupload.pool DROP COLUMN address_id;

View file

@ -56,6 +56,7 @@ type AddressesQuery = *psql.ViewQuery[*Address, AddressSlice]
// addressR is where relationships are stored.
type addressR struct {
Mailers CommsMailerSlice // comms.mailer.mailer_address_id_fkey
Pools FileuploadPoolSlice // fileupload.pool.pool_address_id_fkey
NuisanceOlds PublicreportNuisanceOldSlice // publicreport.nuisance_old.nuisance_address_id_fkey
Reports PublicreportReportSlice // publicreport.report.report_address_id_fkey
WaterOlds PublicreportWaterOldSlice // publicreport.water_old.pool_address_id_fkey
@ -635,6 +636,30 @@ func (os AddressSlice) Mailers(mods ...bob.Mod[*dialect.SelectQuery]) CommsMaile
)...)
}
// Pools starts a query for related objects on fileupload.pool
func (o *Address) Pools(mods ...bob.Mod[*dialect.SelectQuery]) FileuploadPoolsQuery {
return FileuploadPools.Query(append(mods,
sm.Where(FileuploadPools.Columns.AddressID.EQ(psql.Arg(o.ID))),
)...)
}
func (os AddressSlice) Pools(mods ...bob.Mod[*dialect.SelectQuery]) FileuploadPoolsQuery {
pkID := make(pgtypes.Array[int32], 0, len(os))
for _, o := range os {
if o == nil {
continue
}
pkID = append(pkID, o.ID)
}
PKArgExpr := psql.Select(sm.Columns(
psql.F("unnest", psql.Cast(psql.Arg(pkID), "integer[]")),
))
return FileuploadPools.Query(append(mods,
sm.Where(psql.Group(FileuploadPools.Columns.AddressID).OP("IN", PKArgExpr)),
)...)
}
// NuisanceOlds starts a query for related objects on publicreport.nuisance_old
func (o *Address) NuisanceOlds(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportNuisanceOldsQuery {
return PublicreportNuisanceOlds.Query(append(mods,
@ -823,6 +848,74 @@ func (address0 *Address) AttachMailers(ctx context.Context, exec bob.Executor, r
return nil
}
func insertAddressPools0(ctx context.Context, exec bob.Executor, fileuploadPools1 []*FileuploadPoolSetter, address0 *Address) (FileuploadPoolSlice, error) {
for i := range fileuploadPools1 {
fileuploadPools1[i].AddressID = omitnull.From(address0.ID)
}
ret, err := FileuploadPools.Insert(bob.ToMods(fileuploadPools1...)).All(ctx, exec)
if err != nil {
return ret, fmt.Errorf("insertAddressPools0: %w", err)
}
return ret, nil
}
func attachAddressPools0(ctx context.Context, exec bob.Executor, count int, fileuploadPools1 FileuploadPoolSlice, address0 *Address) (FileuploadPoolSlice, error) {
setter := &FileuploadPoolSetter{
AddressID: omitnull.From(address0.ID),
}
err := fileuploadPools1.UpdateAll(ctx, exec, *setter)
if err != nil {
return nil, fmt.Errorf("attachAddressPools0: %w", err)
}
return fileuploadPools1, nil
}
func (address0 *Address) InsertPools(ctx context.Context, exec bob.Executor, related ...*FileuploadPoolSetter) error {
if len(related) == 0 {
return nil
}
var err error
fileuploadPools1, err := insertAddressPools0(ctx, exec, related, address0)
if err != nil {
return err
}
address0.R.Pools = append(address0.R.Pools, fileuploadPools1...)
for _, rel := range fileuploadPools1 {
rel.R.Address = address0
}
return nil
}
func (address0 *Address) AttachPools(ctx context.Context, exec bob.Executor, related ...*FileuploadPool) error {
if len(related) == 0 {
return nil
}
var err error
fileuploadPools1 := FileuploadPoolSlice(related)
_, err = attachAddressPools0(ctx, exec, len(related), fileuploadPools1, address0)
if err != nil {
return err
}
address0.R.Pools = append(address0.R.Pools, fileuploadPools1...)
for _, rel := range related {
rel.R.Address = address0
}
return nil
}
func insertAddressNuisanceOlds0(ctx context.Context, exec bob.Executor, publicreportNuisanceOlds1 []*PublicreportNuisanceOldSetter, address0 *Address) (PublicreportNuisanceOldSlice, error) {
for i := range publicreportNuisanceOlds1 {
publicreportNuisanceOlds1[i].AddressID = omitnull.From(address0.ID)
@ -1203,6 +1296,20 @@ func (o *Address) Preload(name string, retrieved any) error {
o.R.Mailers = rels
for _, rel := range rels {
if rel != nil {
rel.R.Address = o
}
}
return nil
case "Pools":
rels, ok := retrieved.(FileuploadPoolSlice)
if !ok {
return fmt.Errorf("address cannot load %T as %q", retrieved, name)
}
o.R.Pools = rels
for _, rel := range rels {
if rel != nil {
rel.R.Address = o
@ -1306,6 +1413,7 @@ func buildAddressPreloader() addressPreloader {
type addressThenLoader[Q orm.Loadable] struct {
Mailers func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
Pools func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
NuisanceOlds func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
Reports func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
WaterOlds func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
@ -1317,6 +1425,9 @@ func buildAddressThenLoader[Q orm.Loadable]() addressThenLoader[Q] {
type MailersLoadInterface interface {
LoadMailers(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error
}
type PoolsLoadInterface interface {
LoadPools(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error
}
type NuisanceOldsLoadInterface interface {
LoadNuisanceOlds(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error
}
@ -1340,6 +1451,12 @@ func buildAddressThenLoader[Q orm.Loadable]() addressThenLoader[Q] {
return retrieved.LoadMailers(ctx, exec, mods...)
},
),
Pools: thenLoadBuilder[Q](
"Pools",
func(ctx context.Context, exec bob.Executor, retrieved PoolsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error {
return retrieved.LoadPools(ctx, exec, mods...)
},
),
NuisanceOlds: thenLoadBuilder[Q](
"NuisanceOlds",
func(ctx context.Context, exec bob.Executor, retrieved NuisanceOldsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error {
@ -1434,6 +1551,70 @@ func (os AddressSlice) LoadMailers(ctx context.Context, exec bob.Executor, mods
return nil
}
// LoadPools loads the address's Pools into the .R struct
func (o *Address) LoadPools(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error {
if o == nil {
return nil
}
// Reset the relationship
o.R.Pools = nil
related, err := o.Pools(mods...).All(ctx, exec)
if err != nil {
return err
}
for _, rel := range related {
rel.R.Address = o
}
o.R.Pools = related
return nil
}
// LoadPools loads the address's Pools into the .R struct
func (os AddressSlice) LoadPools(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error {
if len(os) == 0 {
return nil
}
fileuploadPools, err := os.Pools(mods...).All(ctx, exec)
if err != nil {
return err
}
for _, o := range os {
if o == nil {
continue
}
o.R.Pools = nil
}
for _, o := range os {
if o == nil {
continue
}
for _, rel := range fileuploadPools {
if !rel.AddressID.IsValue() {
continue
}
if !(rel.AddressID.IsValue() && o.ID == rel.AddressID.MustGet()) {
continue
}
rel.R.Address = o
o.R.Pools = append(o.R.Pools, rel)
}
}
return nil
}
// LoadNuisanceOlds loads the address's NuisanceOlds into the .R struct
func (o *Address) LoadNuisanceOlds(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error {
if o == nil {

View file

@ -49,6 +49,7 @@ type FileuploadPool struct {
AddressLocality string `db:"address_locality" `
AddressRegion string `db:"address_region" `
Condition enums.Poolconditiontype `db:"condition" `
AddressID null.Val[int32] `db:"address_id" `
R fileuploadPoolR `db:"-" `
}
@ -65,6 +66,7 @@ type FileuploadPoolsQuery = *psql.ViewQuery[*FileuploadPool, FileuploadPoolSlice
// fileuploadPoolR is where relationships are stored.
type fileuploadPoolR struct {
Address *Address // fileupload.pool.pool_address_id_fkey
CreatorUser *User // fileupload.pool.pool_creator_id_fkey
CSVFileCSV *FileuploadCSV // fileupload.pool.pool_csv_file_fkey
PropertyOwnerPhoneE164Phone *CommsPhone // fileupload.pool.pool_property_owner_phone_e164_fkey
@ -74,7 +76,7 @@ type fileuploadPoolR struct {
func buildFileuploadPoolColumns(alias string) fileuploadPoolColumns {
return fileuploadPoolColumns{
ColumnsExpr: expr.NewColumnsExpr(
"address_postal_code", "address_street", "committed", "created", "creator_id", "csv_file", "deleted", "geom", "h3cell", "id", "is_in_district", "is_new", "notes", "property_owner_name", "property_owner_phone_e164", "resident_owned", "resident_phone_e164", "line_number", "tags", "address_number", "address_locality", "address_region", "condition",
"address_postal_code", "address_street", "committed", "created", "creator_id", "csv_file", "deleted", "geom", "h3cell", "id", "is_in_district", "is_new", "notes", "property_owner_name", "property_owner_phone_e164", "resident_owned", "resident_phone_e164", "line_number", "tags", "address_number", "address_locality", "address_region", "condition", "address_id",
).WithParent("fileupload.pool"),
tableAlias: alias,
AddressPostalCode: psql.Quote(alias, "address_postal_code"),
@ -100,6 +102,7 @@ func buildFileuploadPoolColumns(alias string) fileuploadPoolColumns {
AddressLocality: psql.Quote(alias, "address_locality"),
AddressRegion: psql.Quote(alias, "address_region"),
Condition: psql.Quote(alias, "condition"),
AddressID: psql.Quote(alias, "address_id"),
}
}
@ -129,6 +132,7 @@ type fileuploadPoolColumns struct {
AddressLocality psql.Expression
AddressRegion psql.Expression
Condition psql.Expression
AddressID psql.Expression
}
func (c fileuploadPoolColumns) Alias() string {
@ -166,10 +170,11 @@ type FileuploadPoolSetter struct {
AddressLocality omit.Val[string] `db:"address_locality" `
AddressRegion omit.Val[string] `db:"address_region" `
Condition omit.Val[enums.Poolconditiontype] `db:"condition" `
AddressID omitnull.Val[int32] `db:"address_id" `
}
func (s FileuploadPoolSetter) SetColumns() []string {
vals := make([]string, 0, 23)
vals := make([]string, 0, 24)
if s.AddressPostalCode.IsValue() {
vals = append(vals, "address_postal_code")
}
@ -239,6 +244,9 @@ func (s FileuploadPoolSetter) SetColumns() []string {
if s.Condition.IsValue() {
vals = append(vals, "condition")
}
if !s.AddressID.IsUnset() {
vals = append(vals, "address_id")
}
return vals
}
@ -312,6 +320,9 @@ func (s FileuploadPoolSetter) Overwrite(t *FileuploadPool) {
if s.Condition.IsValue() {
t.Condition = s.Condition.MustGet()
}
if !s.AddressID.IsUnset() {
t.AddressID = s.AddressID.MustGetNull()
}
}
func (s *FileuploadPoolSetter) Apply(q *dialect.InsertQuery) {
@ -320,7 +331,7 @@ func (s *FileuploadPoolSetter) Apply(q *dialect.InsertQuery) {
})
q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) {
vals := make([]bob.Expression, 23)
vals := make([]bob.Expression, 24)
if s.AddressPostalCode.IsValue() {
vals[0] = psql.Arg(s.AddressPostalCode.MustGet())
} else {
@ -459,6 +470,12 @@ func (s *FileuploadPoolSetter) Apply(q *dialect.InsertQuery) {
vals[22] = psql.Raw("DEFAULT")
}
if !s.AddressID.IsUnset() {
vals[23] = psql.Arg(s.AddressID.MustGetNull())
} else {
vals[23] = psql.Raw("DEFAULT")
}
return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "")
}))
}
@ -468,7 +485,7 @@ func (s FileuploadPoolSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] {
}
func (s FileuploadPoolSetter) Expressions(prefix ...string) []bob.Expression {
exprs := make([]bob.Expression, 0, 23)
exprs := make([]bob.Expression, 0, 24)
if s.AddressPostalCode.IsValue() {
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
@ -631,6 +648,13 @@ func (s FileuploadPoolSetter) Expressions(prefix ...string) []bob.Expression {
}})
}
if !s.AddressID.IsUnset() {
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
psql.Quote(append(prefix, "address_id")...),
psql.Arg(s.AddressID),
}})
}
return exprs
}
@ -857,6 +881,30 @@ func (o FileuploadPoolSlice) ReloadAll(ctx context.Context, exec bob.Executor) e
return nil
}
// Address starts a query for related objects on address
func (o *FileuploadPool) Address(mods ...bob.Mod[*dialect.SelectQuery]) AddressesQuery {
return Addresses.Query(append(mods,
sm.Where(Addresses.Columns.ID.EQ(psql.Arg(o.AddressID))),
)...)
}
func (os FileuploadPoolSlice) Address(mods ...bob.Mod[*dialect.SelectQuery]) AddressesQuery {
pkAddressID := make(pgtypes.Array[null.Val[int32]], 0, len(os))
for _, o := range os {
if o == nil {
continue
}
pkAddressID = append(pkAddressID, o.AddressID)
}
PKArgExpr := psql.Select(sm.Columns(
psql.F("unnest", psql.Cast(psql.Arg(pkAddressID), "integer[]")),
))
return Addresses.Query(append(mods,
sm.Where(psql.Group(Addresses.Columns.ID).OP("IN", PKArgExpr)),
)...)
}
// CreatorUser starts a query for related objects on user_
func (o *FileuploadPool) CreatorUser(mods ...bob.Mod[*dialect.SelectQuery]) UsersQuery {
return Users.Query(append(mods,
@ -953,6 +1001,54 @@ func (os FileuploadPoolSlice) ResidentPhoneE164Phone(mods ...bob.Mod[*dialect.Se
)...)
}
func attachFileuploadPoolAddress0(ctx context.Context, exec bob.Executor, count int, fileuploadPool0 *FileuploadPool, address1 *Address) (*FileuploadPool, error) {
setter := &FileuploadPoolSetter{
AddressID: omitnull.From(address1.ID),
}
err := fileuploadPool0.Update(ctx, exec, setter)
if err != nil {
return nil, fmt.Errorf("attachFileuploadPoolAddress0: %w", err)
}
return fileuploadPool0, nil
}
func (fileuploadPool0 *FileuploadPool) InsertAddress(ctx context.Context, exec bob.Executor, related *AddressSetter) error {
var err error
address1, err := Addresses.Insert(related).One(ctx, exec)
if err != nil {
return fmt.Errorf("inserting related objects: %w", err)
}
_, err = attachFileuploadPoolAddress0(ctx, exec, 1, fileuploadPool0, address1)
if err != nil {
return err
}
fileuploadPool0.R.Address = address1
address1.R.Pools = append(address1.R.Pools, fileuploadPool0)
return nil
}
func (fileuploadPool0 *FileuploadPool) AttachAddress(ctx context.Context, exec bob.Executor, address1 *Address) error {
var err error
_, err = attachFileuploadPoolAddress0(ctx, exec, 1, fileuploadPool0, address1)
if err != nil {
return err
}
fileuploadPool0.R.Address = address1
address1.R.Pools = append(address1.R.Pools, fileuploadPool0)
return nil
}
func attachFileuploadPoolCreatorUser0(ctx context.Context, exec bob.Executor, count int, fileuploadPool0 *FileuploadPool, user1 *User) (*FileuploadPool, error) {
setter := &FileuploadPoolSetter{
CreatorID: omit.From(user1.ID),
@ -1169,6 +1265,7 @@ type fileuploadPoolWhere[Q psql.Filterable] struct {
AddressLocality psql.WhereMod[Q, string]
AddressRegion psql.WhereMod[Q, string]
Condition psql.WhereMod[Q, enums.Poolconditiontype]
AddressID psql.WhereNullMod[Q, int32]
}
func (fileuploadPoolWhere[Q]) AliasedAs(alias string) fileuploadPoolWhere[Q] {
@ -1200,6 +1297,7 @@ func buildFileuploadPoolWhere[Q psql.Filterable](cols fileuploadPoolColumns) fil
AddressLocality: psql.Where[Q, string](cols.AddressLocality),
AddressRegion: psql.Where[Q, string](cols.AddressRegion),
Condition: psql.Where[Q, enums.Poolconditiontype](cols.Condition),
AddressID: psql.WhereNull[Q, int32](cols.AddressID),
}
}
@ -1209,6 +1307,18 @@ func (o *FileuploadPool) Preload(name string, retrieved any) error {
}
switch name {
case "Address":
rel, ok := retrieved.(*Address)
if !ok {
return fmt.Errorf("fileuploadPool cannot load %T as %q", retrieved, name)
}
o.R.Address = rel
if rel != nil {
rel.R.Pools = FileuploadPoolSlice{o}
}
return nil
case "CreatorUser":
rel, ok := retrieved.(*User)
if !ok {
@ -1263,6 +1373,7 @@ func (o *FileuploadPool) Preload(name string, retrieved any) error {
}
type fileuploadPoolPreloader struct {
Address func(...psql.PreloadOption) psql.Preloader
CreatorUser func(...psql.PreloadOption) psql.Preloader
CSVFileCSV func(...psql.PreloadOption) psql.Preloader
PropertyOwnerPhoneE164Phone func(...psql.PreloadOption) psql.Preloader
@ -1271,6 +1382,19 @@ type fileuploadPoolPreloader struct {
func buildFileuploadPoolPreloader() fileuploadPoolPreloader {
return fileuploadPoolPreloader{
Address: func(opts ...psql.PreloadOption) psql.Preloader {
return psql.Preload[*Address, AddressSlice](psql.PreloadRel{
Name: "Address",
Sides: []psql.PreloadSide{
{
From: FileuploadPools,
To: Addresses,
FromColumns: []string{"address_id"},
ToColumns: []string{"id"},
},
},
}, Addresses.Columns.Names(), opts...)
},
CreatorUser: func(opts ...psql.PreloadOption) psql.Preloader {
return psql.Preload[*User, UserSlice](psql.PreloadRel{
Name: "CreatorUser",
@ -1327,6 +1451,7 @@ func buildFileuploadPoolPreloader() fileuploadPoolPreloader {
}
type fileuploadPoolThenLoader[Q orm.Loadable] struct {
Address func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
CreatorUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
CSVFileCSV func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
PropertyOwnerPhoneE164Phone func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
@ -1334,6 +1459,9 @@ type fileuploadPoolThenLoader[Q orm.Loadable] struct {
}
func buildFileuploadPoolThenLoader[Q orm.Loadable]() fileuploadPoolThenLoader[Q] {
type AddressLoadInterface interface {
LoadAddress(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error
}
type CreatorUserLoadInterface interface {
LoadCreatorUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error
}
@ -1348,6 +1476,12 @@ func buildFileuploadPoolThenLoader[Q orm.Loadable]() fileuploadPoolThenLoader[Q]
}
return fileuploadPoolThenLoader[Q]{
Address: thenLoadBuilder[Q](
"Address",
func(ctx context.Context, exec bob.Executor, retrieved AddressLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error {
return retrieved.LoadAddress(ctx, exec, mods...)
},
),
CreatorUser: thenLoadBuilder[Q](
"CreatorUser",
func(ctx context.Context, exec bob.Executor, retrieved CreatorUserLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error {
@ -1375,6 +1509,61 @@ func buildFileuploadPoolThenLoader[Q orm.Loadable]() fileuploadPoolThenLoader[Q]
}
}
// LoadAddress loads the fileuploadPool's Address into the .R struct
func (o *FileuploadPool) LoadAddress(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error {
if o == nil {
return nil
}
// Reset the relationship
o.R.Address = nil
related, err := o.Address(mods...).One(ctx, exec)
if err != nil {
return err
}
related.R.Pools = FileuploadPoolSlice{o}
o.R.Address = related
return nil
}
// LoadAddress loads the fileuploadPool's Address into the .R struct
func (os FileuploadPoolSlice) LoadAddress(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error {
if len(os) == 0 {
return nil
}
addresses, err := os.Address(mods...).All(ctx, exec)
if err != nil {
return err
}
for _, o := range os {
if o == nil {
continue
}
for _, rel := range addresses {
if !o.AddressID.IsValue() {
continue
}
if !(o.AddressID.IsValue() && o.AddressID.MustGet() == rel.ID) {
continue
}
rel.R.Pools = append(rel.R.Pools, o)
o.R.Address = rel
break
}
}
return nil
}
// LoadCreatorUser loads the fileuploadPool's CreatorUser into the .R struct
func (o *FileuploadPool) LoadCreatorUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error {
if o == nil {

View file

@ -161,6 +161,7 @@ func geocodePool(ctx context.Context, txn bob.Tx, client *stadia.StadiaMaps, job
um.Table("fileupload.pool"),
um.SetCol("h3cell").ToArg(geo.Cell),
um.SetCol("geom").To(geom_query),
um.SetCol("address_id").To(*geo.Address.ID),
um.Where(psql.Quote("id").EQ(psql.Arg(pool.ID))),
).Exec(ctx, txn)
if err != nil {

View file

@ -9,6 +9,7 @@ import (
"github.com/Gleipnir-Technology/bob/dialect/psql"
"github.com/Gleipnir-Technology/bob/dialect/psql/dialect"
"github.com/Gleipnir-Technology/bob/dialect/psql/im"
"github.com/Gleipnir-Technology/bob/dialect/psql/sm"
//bobtypes "github.com/Gleipnir-Technology/bob/types"
"github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/h3utils"
@ -124,9 +125,11 @@ func insertAddress(ctx context.Context, txn bob.Executor, address types.Address)
}
return &row.ID, nil
}
func insertAddresses(ctx context.Context, txn bob.Executor, features []stadia.GeocodeFeature) ([]int32, error) {
func insertAddresses(ctx context.Context, txn bob.Executor, features []stadia.GeocodeFeature) ([]types.Address, error) {
query := addressQuery()
for _, feature := range features {
gids := make([]string, len(features))
for i, feature := range features {
gids[i] = feature.Properties.GID
lng := feature.Geometry.Coordinates[0]
lat := feature.Geometry.Coordinates[1]
cell, err := h3utils.GetCell(lng, lat, 15)
@ -148,15 +151,24 @@ func insertAddresses(ctx context.Context, txn bob.Executor, features []stadia.Ge
psql.Arg(feature.Street()),
psql.Raw("''"),
),
im.OnConflict("gid").DoNothing(),
)
}
rows, err := bob.All(ctx, txn, query, scan.StructMapper[_rowWithID]())
_, err := bob.All(ctx, txn, query, scan.StructMapper[_rowWithID]())
if err != nil {
return nil, fmt.Errorf("insert: %w", err)
}
results := make([]int32, len(rows))
for _, row := range rows {
results = append(results, row.ID)
addresses, err := models.Addresses.Query(
sm.Where(
models.Addresses.Columns.Gid.EQ(psql.Any(gids)),
),
).All(ctx, txn)
if err != nil {
return nil, fmt.Errorf("query by gid: %w", err)
}
results := make([]types.Address, len(addresses))
for i, address := range addresses {
results[i] = types.AddressFromModel(address)
}
return results, nil
}

View file

@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/Gleipnir-Technology/bob"
@ -58,8 +57,11 @@ func GeocodeRaw(ctx context.Context, org *models.Organization, address string) (
if err != nil {
return nil, fmt.Errorf("client raw geocode failure on %s: %w", address, err)
}
insertAddresses(ctx, db.PGInstance.BobDB, resp.Features)
return toGeocodeResult(*resp, address)
addresses, err := insertAddresses(ctx, db.PGInstance.BobDB, resp.Features)
if err != nil {
return nil, fmt.Errorf("insert addresses: %w", err)
}
return toGeocodeResult(*resp, address, addresses)
}
func GeocodeStructured(ctx context.Context, org *models.Organization, a types.Address) (*GeocodeResult, error) {
street := fmt.Sprintf("%s %s", a.Number, a.Street)
@ -75,8 +77,11 @@ func GeocodeStructured(ctx context.Context, org *models.Organization, a types.Ad
if err != nil {
return nil, fmt.Errorf("client structured geocode failure on %s: %w", a.String(), err)
}
insertAddresses(ctx, db.PGInstance.BobDB, resp.Features)
return toGeocodeResult(*resp, a.String())
addresses, err := insertAddresses(ctx, db.PGInstance.BobDB, resp.Features)
if err != nil {
return nil, fmt.Errorf("insert addresses: %w", err)
}
return toGeocodeResult(*resp, a.String(), addresses)
}
func ReverseGeocode(ctx context.Context, location types.Location) (*GeocodeResult, error) {
req := stadia.RequestReverseGeocode{
@ -87,54 +92,33 @@ func ReverseGeocode(ctx context.Context, location types.Location) (*GeocodeResul
if err != nil {
return nil, fmt.Errorf("client reverse geocode failure on %s: %w", location.String(), err)
}
insertAddresses(ctx, db.PGInstance.BobDB, resp.Features)
return toGeocodeResult(*resp, location.String())
addresses, err := insertAddresses(ctx, db.PGInstance.BobDB, resp.Features)
if err != nil {
return nil, fmt.Errorf("insert addresses: %w", err)
}
return toGeocodeResult(*resp, location.String(), addresses)
}
func toGeocodeResult(resp stadia.GeocodeResponse, address_msg string) (*GeocodeResult, error) {
func toGeocodeResult(resp stadia.GeocodeResponse, address_msg string, addresses []types.Address) (*GeocodeResult, error) {
if len(resp.Features) < 1 {
return nil, fmt.Errorf("%s matched no locations", address_msg)
}
feature := resp.Features[0]
if len(addresses) < 1 {
return nil, fmt.Errorf("no addresses")
}
if len(resp.Features) > 1 {
if !allFeaturesIdenticalEnough(resp.Features) {
return nil, fmt.Errorf("%s matched more than one location, and they differ a lot", address_msg)
}
}
feature := resp.Features[0]
address := addresses[0]
if feature.Geometry.Type != "Point" {
return nil, fmt.Errorf("wrong type %s from %s", feature.Geometry.Type, address_msg)
}
longitude := feature.Geometry.Coordinates[0]
latitude := feature.Geometry.Coordinates[1]
cell, err := h3utils.GetCell(longitude, latitude, 15)
cell, err := h3utils.GetCell(address.Location.Longitude, address.Location.Latitude, 15)
if err != nil {
return nil, fmt.Errorf("failed to convert lat %f lng %f to h3 cell", longitude, latitude)
}
country_s := strings.ToLower(feature.Properties.CountryA)
// Depending on what kind of request we made we'll get wildly different result structures
// This first structure generally works for forword geocoding
address := types.Address{
Country: country_s,
GID: feature.Properties.GID,
Locality: feature.Properties.Locality,
Location: &types.Location{
Longitude: feature.Geometry.Coordinates[0],
Latitude: feature.Geometry.Coordinates[1],
},
Number: feature.Properties.HouseNumber,
PostalCode: feature.Properties.PostalCode,
Region: feature.Properties.Region,
Raw: feature.Properties.FormattedAddressLine,
Street: feature.Properties.Street,
Unit: "",
}
// If we don't have a locality, try populating for reverse geocoding
if address.Country == "" {
address.Country = strings.ToLower(feature.Properties.Context.ISO3166A3)
address.Locality = feature.Properties.Context.WhosOnFirst.Locality.Name
address.Number = feature.Properties.AddressComponents.Number
address.PostalCode = feature.Properties.AddressComponents.PostalCode
address.Street = feature.Properties.AddressComponents.Street
return nil, fmt.Errorf("failed to convert lat %f lng %f to h3 cell", address.Location.Longitude, address.Location.Latitude)
}
return &GeocodeResult{
Address: address,

View file

@ -268,7 +268,11 @@ tr.has-error {
{{ titleCase(pool.condition) }}
</span>
</td>
<td>{{ pool.tags?.size || 0 }}</td>
<td>
<ul>
<li v-for="(v, k) in pool.tags">{{ k }}={{ v }}</li>
</ul>
</td>
</tr>
</tbody>
</table>