Amalegeni-Go
 

Example 3 : map and injection

This page introduces the 'injection' keyword (think 'sql-injection'), and shows how to return and use a map of structs and a map of slices of structs.

Slice of strings

This one you have already seen from prior examples:

1
2
3
4
5
6
func SelectMasterIdList(artistLike string) []string
<<  
    select id 
    from   t_master
    where  artist like $1
>>  

Map of structs and injection

The struct is defined as in regular go.

1
2
3
4
5
type Master struct {
    Id string
    Artist string 
    Album string
} 

The function is declared to return a mapping from string to Master struct. Convention: the first field in your sql select statement will be taken as the key of the map.

1
2
3
4
5
6
7
8
9
func SelectMaster( masterIdInList injection ) map[string]Master
<<  
    select  id as key, 
            id, 
            artist, 
            album 
    from    t_master
    where   id in ( masterIdInList )
>>  

Also new here is that the parameter to be passed into the SelectMaster() function is of type 'injection', see in line 1: masterIdInList injection

In the generated code, this parameter will be of type 'string', and on composing the actual sql string that will be sent to the database, the injection parameters will be replaced by their value. Ie. the go code for creating the sql for the above function will look like this (see line 8 for the injected parameter):

1
2
3
4
5
6
7
8
9
var buf bytes.Buffer
buf.WriteString(" select  id as key,") 
buf.WriteString("         id,") 
buf.WriteString("         artist,") 
buf.WriteString("         album") 
buf.WriteString(" from    t_master") 
buf.WriteString(" where   id in ( ") 
buf.WriteString(masterIdInList) 
buf.WriteString(" )") 

Map of slices of structs

Instead of every map value only being a single struct, you can just as well create a mapping to slices of structs:

1
func SelectDetail( masterIdInList injection ) map[string] []Detail

Again: the first field in your select serves as the key of the map. The other fields are put in the struct.

Here's the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Detail struct {
    TrackNo int
    Title string
}

func SelectDetail( masterIdInList injection ) map[string][]Detail
<<
    select  master_id as key, 
            trackno, 
            title
    from   t_detail
    where  master_id in ( masterIdInList )
    order by master_id, trackno
>>

Putting it all together

The Amgo code

File: plain/src/ex03/ex03.amg

As you notice: in the above code snippets the test definitions had been omitted: see the run{{ }} statements after every function below. These definitions are used to generate the test source code: src/ex03/ex03_generated_test.go.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
func SelectMasterIdList(artistLike string) []string
<<  
    select id 
    from   t_master
    where  artist like $1
>>  
run 
{{
    artistLike:="%a%"
}}



type Master struct {
    Id string
    Artist string 
    Album string
} 

func SelectMaster( masterIdInList injection ) map[string]Master
<<  
    select  id as key, 
            id, 
            artist, 
            album 
    from    t_master
    where   id in ( masterIdInList )
>>  
run 
{{
    masterIdInList:="'2a087a04'"
}}



type Detail struct {
    TrackNo int
    Title string
}

func SelectDetail( masterIdInList injection ) map[string][]Detail
<<
    select  master_id as key, 
            trackno, 
            title
    from   t_detail
    where  master_id in ( masterIdInList )
    order by master_id, trackno
>>
run
{{
    masterIdInList:="'2a087a04'"
}}

The Go code

File plain/src/run/ex03/ex03_main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import ( 
    "ex03"
    "fmt" 
    "strings"
) 

func main() { 
      
    // get the list of master ids 
    artistLike:="%a%"
    masterIdList, err:= ex03.SelectMasterIdList(artistLike)
    if err!=nil {
        fmt.Printf("Error: %v\n",err)
        return
    }

    // construct the 'where-in-list' string
    inlist:="'"+strings.Join( masterIdList,"','")+"'" 
    fmt.Printf("\nwhere-in-list = %+q\n\n",inlist)

    // get the master map: string -> Master 
    masterMap,err:= ex03.SelectMaster(inlist) 
    if err!=nil {
        fmt.Printf("Error: %v\n",err)
        return
    }

    // get the detail map: string -> Detail slice
    detailMap,err:= ex03.SelectDetail(inlist) 
    if err!=nil {
        fmt.Printf("Error: %v\n",err)
        return
    }


    // print-it
    for _,id:= range (masterIdList) {  
        m:=masterMap[id] 
        fmt.Printf("* %s / %s\n",m.Artist,m.Album)
        for _,d := range (detailMap[m.Id]) { 
            fmt.Printf("   - %d) %s\n",d.TrackNo,d.Title)
        }
    }
        
}  

Download, compile and run it

Download the zipfile ex03.zip containing all the code needed to run the above example.

As in prior examples, edit the setenv.sh script and execute it:

$ unzip ex03.zip
$ cd plain
$ vi setenv.sh
$ . ./setenv.sh

If need be: change the database connection parameters in the file janitor.go :

$  vi src/janitor/janitor.go

The tables in question can be created with this SQL-script: ddl.sql, and populated with this script: data.sql

All setup? Let's kick off amgo!

Run amgo on the ex03.amg file:

$ amgo ex03.amg
template/se.tpl * src/ex03/ex03.amg 
Generating: src/ex03/ex03_generated_type.go
Generating: src/ex03/ex03_generated_func.go
Generating: src/ex03/ex03_generated_test.go

Test the generated code (the tests were written to src/ex03/ex03_generated_test.go) :

$ go test ex03 -test.v
=== RUN TestSelectMasterIdList
[2a087a04 f40d3812]
--- PASS: TestSelectMasterIdList (0.01 seconds)
=== RUN TestSelectMaster
map[2a087a04:{2a087a04 Frank Zappa Waka Jawaka}]
--- PASS: TestSelectMaster (0.01 seconds)
=== RUN TestSelectDetail
map[2a087a04:[{1 Big Swifty} {2 Your Mouth} {3 It Just Might Be a One-Shot Deal} {4 Waka/Jawaka}]]
--- PASS: TestSelectDetail (0.01 seconds)
PASS
ok      ex03    0.040s

Compile/install the ex03 main go file:

go install run/ex03

Run it:

$ ex03 

where-in-list = "'2a087a04','f40d3812'"

* Frank Zappa / Waka Jawaka
   - 1) Big Swifty
   - 2) Your Mouth
   - 3) It Just Might Be a One-Shot Deal
   - 4) Waka/Jawaka
* Johann Sebastian Bach / Suites for Cello
   - 1) Suite No. 1 in G major, BWV 1007 - I. Prelude (moderato)
   - 2) Suite No. 1 in G major, BWV 1007 - II. Allemande (molto moderato)
   - 3) Suite No. 1 in G major, BWV 1007 - III. Courante (allegro non troppo)
   - 4) Suite No. 1 in G major, BWV 1007 - IV. Sarabande (lento)
   - 5) Suite No. 1 in G major, BWV 1007 - V. Menuetto I & II (allegro moderato)
   - 6) Suite No. 1 in G major, BWV 1007 - VI. Gigue (vivace)
   - 7) Suite No. 2 in D minor, BWV 1008 - I. Praeludium
   - 8) Suite No. 2 in D minor, BWV 1008 - II. Allemande
   - 9) Suite No. 2 in D minor, BWV 1008 - III. Courante
   - 10) Suite No. 2 in D minor, BWV 1008 - IV. Sarabande
   - 11) Suite No. 2 in D minor, BWV 1008 - V. Menuetto I & II
   - 12) Suite No. 2 in D minor, BWV 1008 - VI. Gigue
   - 13) Suite No. 3 in C major, BWV 1009 - I. Praeludium
   - 14) Suite No. 3 in C major, BWV 1009 - II. Allemande
   - 15) Suite No. 3 in C major, BWV 1009 - III. Courante
   - 16) Suite No. 3 in C major, BWV 1009 - IV. Sarabande
   - 17) Suite No. 3 in C major, BWV 1009 - V. Bourree I & II
   - 18) Suite No. 3 in C major, BWV 1009 - VI. Gigue
 
© Willem Moors, 2013 - 2020