1package exql
2
3import (
4	"strings"
5)
6
7type innerJoinT struct {
8	Type  string
9	Table string
10	On    string
11	Using string
12}
13
14// Joins represents the union of different join conditions.
15type Joins struct {
16	Conditions []Fragment
17	hash       hash
18}
19
20var _ = Fragment(&Joins{})
21
22// Hash returns a unique identifier for the struct.
23func (j *Joins) Hash() string {
24	return j.hash.Hash(j)
25}
26
27// Compile transforms the Where into an equivalent SQL representation.
28func (j *Joins) Compile(layout *Template) (compiled string, err error) {
29	if c, ok := layout.Read(j); ok {
30		return c, nil
31	}
32
33	l := len(j.Conditions)
34
35	chunks := make([]string, 0, l)
36
37	if l > 0 {
38		for i := 0; i < l; i++ {
39			chunk, err := j.Conditions[i].Compile(layout)
40			if err != nil {
41				return "", err
42			}
43			chunks = append(chunks, chunk)
44		}
45	}
46
47	compiled = strings.Join(chunks, " ")
48
49	layout.Write(j, compiled)
50
51	return
52}
53
54// JoinConditions creates a Joins object.
55func JoinConditions(joins ...*Join) *Joins {
56	fragments := make([]Fragment, len(joins))
57	for i := range fragments {
58		fragments[i] = joins[i]
59	}
60	return &Joins{Conditions: fragments}
61}
62
63// Join represents a generic JOIN statement.
64type Join struct {
65	Type  string
66	Table Fragment
67	On    Fragment
68	Using Fragment
69	hash  hash
70}
71
72var _ = Fragment(&Join{})
73
74// Hash returns a unique identifier for the struct.
75func (j *Join) Hash() string {
76	return j.hash.Hash(j)
77}
78
79// Compile transforms the Join into its equivalent SQL representation.
80func (j *Join) Compile(layout *Template) (compiled string, err error) {
81	if c, ok := layout.Read(j); ok {
82		return c, nil
83	}
84
85	if j.Table == nil {
86		return "", nil
87	}
88
89	table, err := j.Table.Compile(layout)
90	if err != nil {
91		return "", err
92	}
93
94	on, err := layout.doCompile(j.On)
95	if err != nil {
96		return "", err
97	}
98
99	using, err := layout.doCompile(j.Using)
100	if err != nil {
101		return "", err
102	}
103
104	data := innerJoinT{
105		Type:  j.Type,
106		Table: table,
107		On:    on,
108		Using: using,
109	}
110
111	compiled = layout.MustCompile(layout.JoinLayout, data)
112	layout.Write(j, compiled)
113	return
114}
115
116// On represents JOIN conditions.
117type On Where
118
119var _ = Fragment(&On{})
120
121// Hash returns a unique identifier.
122func (o *On) Hash() string {
123	return o.hash.Hash(o)
124}
125
126// Compile transforms the On into an equivalent SQL representation.
127func (o *On) Compile(layout *Template) (compiled string, err error) {
128	if c, ok := layout.Read(o); ok {
129		return c, nil
130	}
131
132	grouped, err := groupCondition(layout, o.Conditions, layout.MustCompile(layout.ClauseOperator, layout.AndKeyword))
133	if err != nil {
134		return "", err
135	}
136
137	if grouped != "" {
138		compiled = layout.MustCompile(layout.OnLayout, conds{grouped})
139	}
140
141	layout.Write(o, compiled)
142	return
143}
144
145// Using represents a USING function.
146type Using Columns
147
148var _ = Fragment(&Using{})
149
150type usingT struct {
151	Columns string
152}
153
154// Hash returns a unique identifier.
155func (u *Using) Hash() string {
156	return u.hash.Hash(u)
157}
158
159// Compile transforms the Using into an equivalent SQL representation.
160func (u *Using) Compile(layout *Template) (compiled string, err error) {
161	if u == nil {
162		return "", nil
163	}
164
165	if c, ok := layout.Read(u); ok {
166		return c, nil
167	}
168
169	if len(u.Columns) > 0 {
170		c := Columns(*u)
171		columns, err := c.Compile(layout)
172		if err != nil {
173			return "", err
174		}
175		data := usingT{Columns: columns}
176		compiled = layout.MustCompile(layout.UsingLayout, data)
177	}
178
179	layout.Write(u, compiled)
180	return
181}
182