├── .gitignore
├── .vscode
└── launch.json
├── README.md
├── go.mod
├── go.sum
├── godb
├── agg_op.go
├── agg_op_test.go
├── agg_state.go
├── big_join_catalog.txt
├── buffer_pool.go
├── buffer_pool_test.go
├── catalog.go
├── catalog.txt
├── deadlock_test.go
├── delete_op.go
├── delete_op_test.go
├── easy_parser_test.go
├── exprs.go
├── filter_op.go
├── filter_op_test.go
├── go.mod
├── go.sum
├── godberrorcode_string.go
├── heap_file.go
├── heap_file_test.go
├── heap_page.go
├── heap_page_test.go
├── insert_op.go
├── insert_op_test.go
├── join_op.go
├── join_op_test.go
├── join_optimizer.go
├── join_test_catalog.txt
├── lab1_query.go
├── lab1_query_test.go
├── lab1_test.csv
├── limit_op.go
├── limit_op_test.go
├── locking_test.go
├── mem_file.go
├── order_by_op.go
├── order_by_test.go
├── parser.go
├── parser_test.go
├── project_op.go
├── project_op_test.go
├── savedresults
│ ├── q1-easy-result.csv
│ ├── q10-easy-result.csv
│ ├── q11-easy-result.csv
│ ├── q12-easy-result.csv
│ ├── q2-easy-result.csv
│ ├── q3-easy-result.csv
│ ├── q4-easy-result.csv
│ ├── q5-easy-result.csv
│ ├── q6-easy-result.csv
│ ├── q7-easy-result.csv
│ ├── q8-easy-result.csv
│ └── q9-easy-result.csv
├── simple_query_test.go
├── table_stats_lab1.go
├── test_heap_file.csv
├── testdb.txt
├── transaction.go
├── transaction_test.go
├── tuple.go
├── tuple_test.go
├── txn_test_1_1.csv
├── txn_test_300_3.csv
├── types.go
└── value_op.go
├── lab1.md
├── lab2.md
├── lab3.md
└── main.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # If you prefer the allow list template instead of the deny list, see community template:
2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3 | #
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.exe~
7 | *.dll
8 | *.so
9 | *.dylib
10 |
11 | # Test binary, built with `go test -c`
12 | *.test
13 |
14 | # Output of the go coverage tool, specifically when used with LiteIDE
15 | *.out
16 |
17 | # Dependency directories (remove the comment below to include it)
18 | # vendor/
19 |
20 | # Go workspace file
21 | go.work
22 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | /*{
8 | "name": "Launch file",
9 | "type": "go",
10 | "showLog": false,
11 | "logOutput": "dap",
12 | "request": "launch",
13 | "mode": "auto",
14 | "program": "main.go",
15 | "env": {},
16 | "args": []
17 | },
18 | */
19 | {
20 | "name": "Test",
21 | "type": "go",
22 | "request": "launch",
23 | "mode": "test",
24 | "program": "godb/",
25 | "env": {},
26 | "args": ["-test.v"],
27 | "showLog": false,
28 | }
29 |
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 6.5830/6.5831 Labs
2 | ==================
3 |
4 | Git repository for labs in [6.5830/6.5831](http://dsg.csail.mit.edu/6.5830/).
5 |
6 | We will be using git, a source code control tool, to distribute labs in
7 | 6.5830/6.5831. This will allow you to incrementally download the code for the
8 | labs, and for us to push any hot fixes that might be necessary.
9 |
10 | You will also be able to use git to commit and backup your progress on the labs
11 | as you go. Course git repositories will be hosted as a repository in GitHub, a
12 | website that hosts runs git servers for thousands of open source projects. In
13 | our case, your code will be in a private repository that is visible only to you
14 | and course staff.
15 |
16 | This document describes what you need to do to get started with git, and also
17 | download and upload 6.5830/6.5831 labs via GitHub.
18 |
19 | **If you are not a registered student at MIT, you are welcome to follow along,
20 | but we ask you to please keep your solution PRIVATE and not make it
21 | publicly available**
22 |
23 | ## Contents
24 |
25 | - [Learning Git](#learning-git)
26 | - [Setting up GitHub](#setting-up-github)
27 | - [Installing Git](#installing-git)
28 | - [Setting up Git](#setting-up-git)
29 | - [Getting Newly Released Labs](#getting-newly-released-labs)
30 | - [Submitting Labs](#submitting-labs)
31 | - [Word of Caution](#word-of-caution)
32 | - [Help!](#help)
33 |
34 | ## Learning Git
35 |
36 | There are numerous guides on using Git that are available. They range from being interactive to just text-based. Find
37 | one that works and experiment; making mistakes and fixing them is a great way to learn.
38 | Github has compiled a list of such resources
39 | [here][resources].
40 |
41 | If you have no experience with git, you may find
42 | this [Missing Semester lecture][missing-semester-version-control] helpful. And if you want to build an intuition with git commands, try this [card game][omg].
43 |
44 | ## Setting Up GitHub
45 |
46 | Now that you have a basic understanding of Git, it's time to get started with GitHub.
47 |
48 | 0. Install git. (See below for suggestions).
49 |
50 | 1. If you don't already have an account, [sign up for one][join].
51 |
52 | ### Installing git
53 |
54 | The instructions are tested on bash/linux environments. Installing git should be a simple `apt-get install`, `yum install`, or similar.
55 |
56 | Instructions for installing git on Linux, OSX, or Windows can be found at
57 | [GitBook:
58 | Installing][gitbook].
59 |
60 | If you are using an IDE like IntelliJ/VSCode, it likely comes with git integration. The set-up instructions below may be slightly different than the
61 | command line instructions listed, but will work for any OS. Detailed instructions can be found
62 | in
63 | [IntelliJ Help][intellij-help],
64 | [VSCode Version Control Guide][vscode-version-control],
65 | and/or
66 | [VSCode Github Guide][vscode-github].
67 |
68 | ## Setting Up Git
69 |
70 | You should have Git installed from the previous section.
71 |
72 | 1. The first thing we have to do is to clone the current lab repository by issuing the following commands on the command line:
73 |
74 | ```bash
75 | $ git clone https://github.com/MIT-DB-Class/go-db-2024.git
76 | ```
77 |
78 | Now, every time a new lab or patch is released, you can
79 |
80 | ```bash
81 | $ git pull
82 | ```
83 | to get the latest.
84 |
85 | That's it. You can start working on the labs! That said, we strongly encourage you to use git for more than just
86 | downloading the labs. In the rest of the guide we will walk you through on how to use git for version-control
87 | during your own development.
88 |
89 | 2. Notice that you are cloning from our repo, which means that it will be inappropriate for you to push your code to it.
90 | If you want to use git for version control, you will need to create your own repo to write your changes to. Do so
91 | by clicking 'New' on the left in github, and make sure to choose **Private** when creating, so others cannot see your
92 | code! Now we are going to change the repo we just checked out to point to your personal repository.
93 |
94 | 3. By default the remote called `origin` is set to the location that you cloned the repository from. You should see the following:
95 |
96 | ```bash
97 | $ git remote -v
98 | origin https://github.com/MIT-DB-Class/go-db-2024.git (fetch)
99 | origin https://github.com/MIT-DB-Class/go-db-2024.git (push)
100 | ```
101 |
102 | We don't want that remote to be the origin. Instead, we want to change it to point to your repository. To do that, issue the following command:
103 |
104 | ```bash
105 | $ git remote rename origin upstream
106 | ```
107 |
108 | And now you should see the following:
109 |
110 | ```bash
111 | $ git remote -v
112 | upstream https://github.com/MIT-DB-Class/go-db-2024.git (fetch)
113 | upstream https://github.com/MIT-DB-Class/go-db-2024.git (push)
114 | ```
115 |
116 | 4. Lastly we need to give your repository a new `origin` since it is lacking one. Issue the following command, substituting your athena username:
117 |
118 | ```bash
119 | $ git remote add origin https://github.com/[your-username]/[your-repo]
120 | ```
121 |
122 | If you have an error that looks like the following:
123 |
124 | ```
125 | Could not rename config section 'remote.[old name]' to 'remote.[new name]'
126 | ```
127 |
128 | Or this error:
129 |
130 | ```
131 | fatal: remote origin already exists.
132 | ```
133 |
134 | This appears to happen to some depending on the version of Git being used. To fix it, just issue the following command:
135 |
136 | ```bash
137 | $ git remote set-url origin https://github.com/[your-username]/[your-repo]
138 | ```
139 |
140 | This solution was found from [StackOverflow](http://stackoverflow.com/a/2432799) thanks to [Cassidy Williams](https://github.com/cassidoo).
141 |
142 | For reference, your final `git remote -v` should look like following when it's setup correctly:
143 |
144 |
145 | ```bash
146 | $ git remote -v
147 | upstream https://github.com/MIT-DB-Class/go-db-2024.git (fetch)
148 | upstream https://github.com/MIT-DB-Class/go-db-2024.git (push)
149 | origin https://github.com/[your-username]/[your-repo] (fetch)
150 | origin https://github.com/[your-username]/[your-repo] (push)
151 | ```
152 |
153 | 5. Let's test it out by doing a push of your main branch to GitHub by issuing the following:
154 |
155 | ```bash
156 | $ git push -u origin main
157 | ```
158 |
159 | You should see something like the following:
160 |
161 | ```
162 | Counting objects: 59, done.
163 | Delta compression using up to 4 threads.
164 | Compressing objects: 100% (53/53), done.
165 | Writing objects: 100% (59/59), 420.46 KiB | 0 bytes/s, done.
166 | Total 59 (delta 2), reused 59 (delta 2)
167 | remote: Resolving deltas: 100% (2/2), done.
168 | To git@github.com:[your-repo].git
169 | * [new branch] main -> main
170 | Branch main set up to track remote branch main from origin.
171 | ```
172 |
173 |
174 | 6. That last command was a bit special and only needs to be run the first time to setup the remote tracking branches.
175 | Now we should be able to just run `git push` without the arguments. Try it and you should get the following:
176 |
177 | ```bash
178 | $ git push
179 | Everything up-to-date
180 | ```
181 |
182 | If you don't know Git that well, this probably seemed very arcane. Just keep using Git and you'll understand more and
183 | more. You aren't required to use commands like `commit` and `push` as you develop your labs, but will find them useful for
184 | debugging. We'll provide explicit instructions on how to use these commands to actually upload your final lab solution.
185 |
186 | ## Getting Newly Released Labs
187 |
188 | (You don't need to follow these instructions until Lab 1.)
189 |
190 | Pulling in labs that are released or previous lab solutions should be easy as long as you set up your repository based
191 | on the instructions in the last section.
192 |
193 | 1. All new labs will be posted to the [labs repository][labs-github] in the class organization.
194 |
195 | Check it periodically as well as Piazza's announcements for updates on when the new labs are released.
196 |
197 | 2. Once a lab is released, pull in the changes from your godb directory:
198 |
199 | ```bash
200 | $ git pull upstream main
201 | ```
202 |
203 | **OR** if you wish to be more explicit, you can `fetch` first and then `merge`:
204 |
205 | ```bash
206 | $ git fetch upstream
207 | $ git merge upstream/main
208 | ```
209 | Now commit to your main branch:
210 | ```bash
211 | $ git push origin main
212 | ```
213 |
214 | 3. If you've followed the instructions in each lab, you should have no merge conflicts. If you encounter merge conflicts, please reach out to course staff.
215 |
216 | ## Submitting Labs
217 |
218 | We will be using Gradescope to autograde all programming assignments. You should
219 | have all been invited to the class instance; if not, please check Piazza for an
220 | invite code. If you are still having trouble, let us know and we can help you
221 | set up. You may submit your code multiple times before the deadline; we will use
222 | the latest version as determined by Gradescope. Place the write-up in a file
223 | called `lab1-writeup.txt` with your submission.
224 |
225 | If you are working with a partner, only one person needs to submit to
226 | Gradescope. However, make sure to add the other person to your group. Also note
227 | that each member must have their own writeup. Please add your Kerberos username
228 | to the file name and in the writeup itself (e.g., `lab1-writeup-username1.txt`
229 | and `lab1-writeup-username2.txt`).
230 |
231 | The easiest way to submit to Gradescope is with `.zip` files containing your
232 | code. On Linux/macOS, you can do so by running the following command:
233 |
234 | ```bash
235 | $ zip -r submission.zip godb/ lab1-writeup.txt
236 |
237 | # If you are working with a partner:
238 | $ zip -r submission.zip godb/ lab1-writeup-username1.txt lab1-writeup-username2.txt
239 | ```
240 |
241 | ## Submitting a bug
242 |
243 | Please submit (friendly!) bug reports to
244 | [6.5830-staff@mit.edu](mailto:6.5830-staff@mit.edu). When you do, please try to
245 | include:
246 |
247 | * A description of the bug.
248 | * A `.go` file with test functions that we can drop into the `godb` directory, compile, and run.
249 | * A `.txt` file with the data that reproduces the bug.
250 |
251 | If you are the first person to report a particular bug in the code, we will give
252 | you a candy bar!
253 |
254 |
255 |
256 |
257 |
258 | ## Grading
259 |
260 | 75% of your grade will be based on whether or not your code passes the system
261 | test suite we will run over it. These tests will be a superset of the tests we
262 | have provided. Before handing in your code, you should make sure it produces no
263 | errors (passes all of the tests) when you run `go test` in the `godb` directory.
264 |
265 | **Important:** before testing, Gradescope will replace the go test files with our version of these files.
266 | This means you should make sure that your code passes the unmodified tests.
267 |
268 | You should get immediate feedback and error outputs for failed visible tests (if any)
269 | from Gradescope after submission. There may exist several hidden tests (a small percentage) that will not be visible until after the deadline.
270 | The score given will be your grade for the
271 | autograded portion of the assignment. An additional 25% of your grade will be
272 | based on the quality of your writeup and our subjective evaluation of your code.
273 | This part will also be published on Gradescope after we finish grading your
274 | assignment.
275 |
276 | We had a lot of fun designing this assignment, and we hope you enjoy hacking on
277 | it!
278 |
279 | ## Word of Caution
280 |
281 | Git is a distributed version control system. This means everything operates
282 | *offline* until you run `git pull` or `git push`. This is a great feature.
283 |
284 | However, one consequence of this is that you may forget to `git push` your
285 | changes. This is why we **strongly** suggest that you check GitHub to be sure
286 | that what you want to see matches up with what you expect.
287 |
288 | ## Help!
289 |
290 | If at any point you need help with setting all this up, feel free to reach out to one of the TAs or the instructor.
291 | Their contact information can be found on the [course homepage](http://dsg.csail.mit.edu/6.5830/).
292 |
293 | [gitbook]: http://git-scm.com/book/en/Getting-Started-Installing-Git
294 |
295 | [intellij-help]: https://www.jetbrains.com/help/idea/version-control-integration.html
296 |
297 | [join]: https://github.com/join
298 |
299 | [labs-github]: https://github.com/MIT-DB-Class/go-db-2024.git
300 |
301 | [missing-semester-version-control]: https://missing.csail.mit.edu/2020/version-control/
302 |
303 | [omg]: https://ohmygit.org/
304 |
305 | [resources]: https://help.github.com/articles/what-are-other-good-resources-for-learning-git-and-github
306 |
307 | [ssh-key]: https://help.github.com/articles/generating-ssh-keys
308 |
309 | [vscode-github]: https://code.visualstudio.com/docs/editor/github
310 |
311 | [vscode-version-control]: https://code.visualstudio.com/Docs/editor/versioncontrol#_git-support
312 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | go 1.21
4 |
5 | replace github.com/srmadden/godb => ./godb
6 |
7 | require (
8 | github.com/chzyer/readline v1.5.1
9 | github.com/srmadden/godb v0.0.0-00010101000000-000000000000
10 | )
11 |
12 | require (
13 | github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43 // indirect
14 | github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 // indirect
15 | golang.org/x/sys v0.20.0 // indirect
16 | )
17 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
2 | github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
3 | github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
4 | github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
5 | github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
6 | github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
7 | github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
8 | github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
9 | github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43 h1:QEePdg0ty2r0t1+qwfZmQ4OOl/MB2UXIeJSpIZv56lg=
10 | github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43/go.mod h1:OYRfF6eb5wY9VRFkXJH8FFBi3plw2v+giaIu7P054pM=
11 | github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ=
12 | github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
13 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
14 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
15 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
16 |
--------------------------------------------------------------------------------
/godb/agg_op.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | type Aggregator struct {
8 | // Expressions that when applied to tuples from the child operators,
9 | // respectively, return the value of the group by key tuple
10 | groupByFields []Expr
11 |
12 | // Aggregation states that serves as a template as to which types of
13 | // aggregations in which order are to be computed for every group.
14 | newAggState []AggState
15 |
16 | child Operator // the child operator for the inputs to aggregate
17 | }
18 |
19 | type AggType int
20 |
21 | const (
22 | IntAggregator AggType = iota
23 | StringAggregator AggType = iota
24 | )
25 |
26 | const DefaultGroup int = 0 // for handling the case of no group-by
27 |
28 | // Construct an aggregator with a group-by.
29 | func NewGroupedAggregator(emptyAggState []AggState, groupByFields []Expr, child Operator) *Aggregator {
30 | return &Aggregator{groupByFields, emptyAggState, child}
31 | }
32 |
33 | // Construct an aggregator with no group-by.
34 | func NewAggregator(emptyAggState []AggState, child Operator) *Aggregator {
35 | return &Aggregator{nil, emptyAggState, child}
36 | }
37 |
38 | // Return a TupleDescriptor for this aggregation.
39 | //
40 | // If the aggregator has no group-by, the returned descriptor should contain the
41 | // union of the fields in the descriptors of the aggregation states. If the
42 | // aggregator has a group-by, the returned descriptor will additionally start
43 | // with the group-by fields, and then the aggregation states descriptors like
44 | // that without group-by.
45 | //
46 | // HINT: for groupByFields, you can use [Expr.GetExprType] to get the FieldType.
47 | //
48 | // HINT: use [TupleDesc.merge] to merge the two [TupleDesc]s.
49 | func (a *Aggregator) Descriptor() *TupleDesc {
50 | // TODO: some code goes here
51 | return &TupleDesc{} //replace me
52 | }
53 |
54 | // Returns an iterator over the results of the aggregate. The aggregate should
55 | // be the result of aggregating each group's tuples and the iterator should
56 | // iterate through each group's result. In the case where there is no group-by,
57 | // the iterator simply iterates through only one tuple, representing the
58 | // aggregation of all child tuples.
59 | func (a *Aggregator) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
60 | // the child iterator
61 | childIter, err := a.child.Iterator(tid)
62 | if err != nil {
63 | return nil, err
64 | }
65 | if childIter == nil {
66 | return nil, GoDBError{MalformedDataError, "child iter unexpectedly nil"}
67 | }
68 |
69 | // the map that stores the aggregation state of each group
70 | aggState := make(map[any]*[]AggState)
71 | if a.groupByFields == nil {
72 | var newAggState []AggState
73 | for _, as := range a.newAggState {
74 | copy := as.Copy()
75 | if copy == nil {
76 | return nil, GoDBError{MalformedDataError, "aggState Copy unexpectedly returned nil"}
77 | }
78 | newAggState = append(newAggState, copy)
79 | }
80 |
81 | aggState[DefaultGroup] = &newAggState
82 | }
83 |
84 | // the list of group key tuples
85 | var groupByList []*Tuple
86 | // the iterator for iterating thru the finalized aggregation results for each group
87 | var finalizedIter func() (*Tuple, error)
88 |
89 | return func() (*Tuple, error) {
90 | // iterates thru all child tuples
91 | for t, err := childIter(); t != nil || err != nil; t, err = childIter() {
92 | if err != nil {
93 | return nil, err
94 | }
95 | if t == nil {
96 | return nil, nil
97 | }
98 |
99 | if a.groupByFields == nil { // adds tuple to the aggregation in the case of no group-by
100 | for i := 0; i < len(a.newAggState); i++ {
101 | (*aggState[DefaultGroup])[i].AddTuple(t)
102 | }
103 | } else { // adds tuple to the aggregation with grouping
104 | keygenTup, err := extractGroupByKeyTuple(a, t)
105 | if err != nil {
106 | return nil, err
107 | }
108 |
109 | key := keygenTup.tupleKey()
110 | if aggState[key] == nil {
111 | asNew := make([]AggState, len(a.newAggState))
112 | aggState[key] = &asNew
113 | groupByList = append(groupByList, keygenTup)
114 | }
115 |
116 | addTupleToGrpAggState(a, t, aggState[key])
117 | }
118 | }
119 |
120 | if finalizedIter == nil { // builds the iterator for iterating thru the finalized aggregation results for each group
121 | if a.groupByFields == nil {
122 | var tup *Tuple
123 | for i := 0; i < len(a.newAggState); i++ {
124 | newTup := (*aggState[DefaultGroup])[i].Finalize()
125 | tup = joinTuples(tup, newTup)
126 | }
127 | finalizedIter = func() (*Tuple, error) { return nil, nil }
128 | return tup, nil
129 | } else {
130 | finalizedIter = getFinalizedTuplesIterator(a, groupByList, aggState)
131 | }
132 | }
133 | return finalizedIter()
134 | }, nil
135 | }
136 |
137 | // Given a tuple t from a child iterator, return a tuple that identifies t's
138 | // group. The returned tuple should contain the fields from the groupByFields
139 | // list passed into the aggregator constructor. The ith field can be extracted
140 | // from the supplied tuple using the EvalExpr method on the ith expression of
141 | // groupByFields.
142 | //
143 | // If there is any error during expression evaluation, return the error.
144 | func extractGroupByKeyTuple(a *Aggregator, t *Tuple) (*Tuple, error) {
145 | // TODO: some code goes here
146 | return &Tuple{}, fmt.Errorf("extractGroupByKeyTuple not implemented.") // replace me
147 | }
148 |
149 | // Given a tuple t from child and (a pointer to) the array of partially computed
150 | // aggregates grpAggState, add t into all partial aggregations using
151 | // [AggState.AddTuple]. If any of the array elements is of grpAggState is null
152 | // (i.e., because this is the first invocation of this method, create a new
153 | // aggState using [aggState.Copy] on appropriate element of the a.newAggState
154 | // field and add the new aggState to grpAggState.
155 | func addTupleToGrpAggState(a *Aggregator, t *Tuple, grpAggState *[]AggState) {
156 | // TODO: some code goes here
157 | }
158 |
159 | // Given that all child tuples have been added, return an iterator that iterates
160 | // through the finalized aggregate result one group at a time. The returned
161 | // tuples should be structured according to the TupleDesc returned from the
162 | // Descriptor() method.
163 | //
164 | // HINT: you can call [aggState.Finalize] to get the field for each AggState.
165 | // Then, you should get the groupByTuple and merge it with each of the AggState
166 | // tuples using the joinTuples function in tuple.go you wrote in lab 1.
167 | func getFinalizedTuplesIterator(a *Aggregator, groupByList []*Tuple, aggState map[any]*[]AggState) func() (*Tuple, error) {
168 | // TODO: some code goes here
169 | return func() (*Tuple, error) {
170 | // TODO: some code goes here
171 | return nil, fmt.Errorf("getFinalizedTuplesIterator not implemented.") // replace me
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/godb/agg_op_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestAggSimpleSum(t *testing.T) {
8 | _, t1, t2, hf, _, tid := makeTestVars(t)
9 |
10 | err := hf.insertTuple(&t1, tid)
11 | if err != nil {
12 | t.Fatalf(err.Error())
13 | }
14 | err = hf.insertTuple(&t2, tid)
15 | if err != nil {
16 | t.Fatalf(err.Error())
17 | }
18 | sa := SumAggState{}
19 | expr := FieldExpr{t1.Desc.Fields[1]}
20 | err = sa.Init("sum", &expr)
21 | if err != nil {
22 | t.Fatalf(err.Error())
23 | }
24 | agg := NewAggregator([]AggState{&sa}, hf)
25 | iter, err := agg.Iterator(tid)
26 | if err != nil {
27 | t.Fatalf(err.Error())
28 | }
29 | if iter == nil {
30 | t.Fatalf("Iterator was nil")
31 | }
32 | tup, err := iter()
33 | if err != nil {
34 | t.Fatalf(err.Error())
35 | }
36 | if tup == nil {
37 | t.Fatalf("Expected non-null tuple")
38 | }
39 | sum := tup.Fields[0].(IntField).Value
40 | if sum != 1024 {
41 | t.Errorf("unexpected sum")
42 | }
43 | }
44 |
45 | func TestAggMinStringAgg(t *testing.T) {
46 | _, t1, t2, hf, _, tid := makeTestVars(t)
47 | err := hf.insertTuple(&t1, tid)
48 | if err != nil {
49 | t.Fatalf(err.Error())
50 | }
51 | err = hf.insertTuple(&t2, tid)
52 | if err != nil {
53 | t.Fatalf(err.Error())
54 | }
55 | sa := MinAggState{}
56 | expr := FieldExpr{t1.Desc.Fields[0]}
57 | err = sa.Init("min", &expr)
58 | if err != nil {
59 | t.Fatalf(err.Error())
60 | }
61 | agg := NewAggregator([]AggState{&sa}, hf)
62 | iter, err := agg.Iterator(tid)
63 | if err != nil {
64 | t.Fatalf(err.Error())
65 | }
66 | if iter == nil {
67 | t.Fatalf("Iterator was nil")
68 | }
69 | tup, err := iter()
70 | if err != nil {
71 | t.Fatalf(err.Error())
72 | }
73 | if tup == nil {
74 | t.Fatalf("Expected non-null tuple")
75 | }
76 | min := tup.Fields[0].(StringField).Value
77 | if min != "george jones" {
78 | t.Errorf("incorrect min")
79 | }
80 | }
81 |
82 | func TestAggSimpleCount(t *testing.T) {
83 | _, t1, t2, hf, _, tid := makeTestVars(t)
84 | err := hf.insertTuple(&t1, tid)
85 | if err != nil {
86 | t.Fatalf(err.Error())
87 | }
88 | err = hf.insertTuple(&t2, tid)
89 | if err != nil {
90 | t.Fatalf(err.Error())
91 | }
92 | sa := CountAggState{}
93 | expr := FieldExpr{t1.Desc.Fields[0]}
94 | err = sa.Init("count", &expr)
95 | if err != nil {
96 | t.Fatalf(err.Error())
97 | }
98 | agg := NewAggregator([]AggState{&sa}, hf)
99 | iter, err := agg.Iterator(tid)
100 | if err != nil {
101 | t.Fatalf(err.Error())
102 | }
103 | if iter == nil {
104 | t.Fatalf("Iterator was nil")
105 | }
106 | tup, err := iter()
107 | if err != nil {
108 | t.Fatalf(err.Error())
109 | }
110 | if tup == nil {
111 | t.Fatalf("Expected non-null tuple")
112 | }
113 | cnt := tup.Fields[0].(IntField).Value
114 | if cnt != 2 {
115 | t.Errorf("unexpected count")
116 | }
117 | }
118 |
119 | func TestAggMulti(t *testing.T) {
120 | _, t1, t2, hf, _, tid := makeTestVars(t)
121 | err := hf.insertTuple(&t1, tid)
122 | if err != nil {
123 | t.Fatalf(err.Error())
124 | }
125 | err = hf.insertTuple(&t2, tid)
126 | if err != nil {
127 | t.Fatalf(err.Error())
128 | }
129 | ca := CountAggState{}
130 | expr := FieldExpr{t1.Desc.Fields[0]}
131 | err = ca.Init("count", &expr)
132 | if err != nil {
133 | t.Fatalf(err.Error())
134 | }
135 | sa := SumAggState{}
136 | expr = FieldExpr{t1.Desc.Fields[1]}
137 | err = sa.Init("sum", &expr)
138 | if err != nil {
139 | t.Fatalf(err.Error())
140 | }
141 | agg := NewAggregator([]AggState{&ca, &sa}, hf)
142 | iter, err := agg.Iterator(tid)
143 | if err != nil {
144 | t.Fatalf(err.Error())
145 | }
146 | if iter == nil {
147 | t.Fatalf("Iterator was nil")
148 | }
149 | tup, err := iter()
150 | if err != nil {
151 | t.Fatalf(err.Error())
152 | }
153 | if tup == nil {
154 | t.Fatalf("Expected non-null tuple")
155 | }
156 | cnt := tup.Fields[0].(IntField).Value
157 | if cnt != 2 {
158 | t.Errorf("unexpected count")
159 | }
160 | sum := tup.Fields[1].(IntField).Value
161 | if sum != 1024 {
162 | t.Errorf("unexpected sum")
163 | }
164 | }
165 |
166 | func TestAggGbyCount(t *testing.T) {
167 | _, t1, t2, hf, _, tid := makeTestVars(t)
168 | err := hf.insertTuple(&t1, tid)
169 | if err != nil {
170 | t.Fatalf(err.Error())
171 | }
172 | err = hf.insertTuple(&t2, tid)
173 | if err != nil {
174 | t.Fatalf(err.Error())
175 | }
176 | err = hf.insertTuple(&t2, tid)
177 | if err != nil {
178 | t.Fatalf(err.Error())
179 | }
180 | err = hf.insertTuple(&t2, tid)
181 | if err != nil {
182 | t.Fatalf(err.Error())
183 | }
184 | gbyFields := []Expr{&FieldExpr{hf.Descriptor().Fields[0]}}
185 | sa := CountAggState{}
186 | expr := FieldExpr{t1.Desc.Fields[0]}
187 | err = sa.Init("count", &expr)
188 | if err != nil {
189 | t.Fatalf(err.Error())
190 | }
191 |
192 | agg := NewGroupedAggregator([]AggState{&sa}, gbyFields, hf)
193 | iter, _ := agg.Iterator(tid)
194 | fields := []FieldType{
195 | {"name", "", StringType},
196 | {"count", "", IntType},
197 | }
198 | outt1 := Tuple{TupleDesc{fields},
199 | []DBValue{
200 | StringField{"sam"},
201 | IntField{1},
202 | },
203 | nil,
204 | }
205 | outt2 := Tuple{
206 | TupleDesc{fields},
207 | []DBValue{
208 | StringField{"george jones"},
209 | IntField{3},
210 | },
211 | nil,
212 | }
213 | ts := []*Tuple{&outt1, &outt2}
214 | err = CheckIfOutputMatches(iter, ts)
215 | if err != nil {
216 | t.Fatalf(err.Error())
217 | }
218 | }
219 |
220 | func TestAggGbySum(t *testing.T) {
221 | _, t1, t2, hf, _, tid := makeTestVars(t)
222 | err := hf.insertTuple(&t1, tid)
223 | if err != nil {
224 | t.Fatalf(err.Error())
225 | }
226 | err = hf.insertTuple(&t2, tid)
227 | if err != nil {
228 | t.Fatalf(err.Error())
229 | }
230 | err = hf.insertTuple(&t1, tid)
231 | if err != nil {
232 | t.Fatalf(err.Error())
233 | }
234 | err = hf.insertTuple(&t2, tid)
235 | if err != nil {
236 | t.Fatalf(err.Error())
237 | }
238 | //gbyFields := hf.td.Fields[0:1]
239 | gbyFields := []Expr{&FieldExpr{hf.Descriptor().Fields[0]}}
240 |
241 | sa := SumAggState{}
242 | expr := FieldExpr{t1.Desc.Fields[1]}
243 | err = sa.Init("sum", &expr)
244 | if err != nil {
245 | t.Fatalf(err.Error())
246 | }
247 |
248 | agg := NewGroupedAggregator([]AggState{&sa}, gbyFields, hf)
249 | iter, _ := agg.Iterator(tid)
250 |
251 | fields := []FieldType{
252 | {"name", "", StringType},
253 | {"sum", "", IntType},
254 | }
255 | outt1 := Tuple{TupleDesc{fields},
256 | []DBValue{
257 | StringField{"sam"},
258 | IntField{50},
259 | }, nil,
260 | }
261 | outt2 := Tuple{
262 | TupleDesc{fields},
263 | []DBValue{
264 | StringField{"george jones"},
265 | IntField{1998},
266 | }, nil,
267 | }
268 | ts := []*Tuple{&outt1, &outt2}
269 | err = CheckIfOutputMatches(iter, ts)
270 | if err != nil {
271 | t.Fatalf(err.Error())
272 | }
273 | }
274 |
275 | func TestAggFilterCount(t *testing.T) {
276 | _, t1, t2, hf, _, tid := makeTestVars(t)
277 | err := hf.insertTuple(&t1, tid)
278 | if err != nil {
279 | t.Fatalf(err.Error())
280 | }
281 | err = hf.insertTuple(&t2, tid)
282 | if err != nil {
283 | t.Fatalf(err.Error())
284 | }
285 |
286 | var f FieldType = FieldType{"age", "", IntType}
287 | filt, err := NewFilter(&ConstExpr{IntField{25}, IntType}, OpGt, &FieldExpr{f}, hf)
288 | if err != nil {
289 | t.Fatalf(err.Error())
290 | }
291 | if filt == nil {
292 | t.Fatalf("Filter returned nil")
293 | }
294 |
295 | sa := CountAggState{}
296 | expr := FieldExpr{t1.Desc.Fields[0]}
297 | err = sa.Init("count", &expr)
298 | if err != nil {
299 | t.Fatalf(err.Error())
300 | }
301 | agg := NewAggregator([]AggState{&sa}, filt)
302 | iter, err := agg.Iterator(tid)
303 | if err != nil {
304 | t.Fatalf(err.Error())
305 | }
306 | if iter == nil {
307 | t.Fatalf("Iterator was nil")
308 | }
309 | tup, err := iter()
310 | if err != nil {
311 | t.Fatalf(err.Error())
312 | }
313 | if tup == nil {
314 | t.Fatalf("Expected non-null tuple")
315 | }
316 | cnt := tup.Fields[0].(IntField).Value
317 | if cnt != 1 {
318 | t.Errorf("unexpected count")
319 | }
320 | }
321 |
322 | func TestAggRepeatedIteration(t *testing.T) {
323 | _, t1, t2, hf, _, tid := makeTestVars(t)
324 | err := hf.insertTuple(&t1, tid)
325 | if err != nil {
326 | t.Fatalf(err.Error())
327 | }
328 | err = hf.insertTuple(&t2, tid)
329 | if err != nil {
330 | t.Fatalf(err.Error())
331 | }
332 | sa := CountAggState{}
333 | expr := FieldExpr{t1.Desc.Fields[0]}
334 | err = sa.Init("count", &expr)
335 | if err != nil {
336 | t.Fatalf(err.Error())
337 | }
338 | agg := NewAggregator([]AggState{&sa}, hf)
339 | iter, err := agg.Iterator(tid)
340 | if err != nil {
341 | t.Fatalf(err.Error())
342 | }
343 | if iter == nil {
344 | t.Fatalf("Iterator was nil")
345 | }
346 | tup, err := iter()
347 | if err != nil {
348 | t.Fatalf(err.Error())
349 | }
350 | if tup == nil {
351 | t.Fatalf("Expected non-null tuple")
352 | }
353 | cnt := tup.Fields[0].(IntField).Value
354 | if cnt != 2 {
355 | t.Errorf("unexpected count")
356 | }
357 | iter, err = agg.Iterator(tid)
358 | if err != nil {
359 | t.Fatalf(err.Error())
360 | }
361 | if iter == nil {
362 | t.Fatalf("Iterator was nil")
363 | }
364 | tup, err = iter()
365 | if err != nil {
366 | t.Fatalf(err.Error())
367 | }
368 | if tup == nil {
369 | t.Fatalf("Expected non-null tuple")
370 | }
371 | cnt2 := tup.Fields[0].(IntField).Value
372 | if cnt != cnt2 {
373 | t.Errorf("count changed on repeated iteration")
374 | }
375 | }
376 |
--------------------------------------------------------------------------------
/godb/agg_state.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // interface for an aggregation state
8 | type AggState interface {
9 | // Initializes an aggregation state. Is supplied with an alias, an expr to
10 | // evaluate an input tuple into a DBValue, and a getter to extract from the
11 | // DBValue its int or string field's value.
12 | Init(alias string, expr Expr) error
13 |
14 | // Makes an copy of the aggregation state.
15 | Copy() AggState
16 |
17 | // Adds an tuple to the aggregation state.
18 | AddTuple(*Tuple)
19 |
20 | // Returns the final result of the aggregation as a tuple.
21 | Finalize() *Tuple
22 |
23 | // Gets the tuple description of the tuple that Finalize() returns.
24 | GetTupleDesc() *TupleDesc
25 | }
26 |
27 | // Implements the aggregation state for COUNT
28 | // We are supplying the implementation of CountAggState as an example. You need to
29 | // implement the rest of the aggregation states.
30 | type CountAggState struct {
31 | alias string
32 | expr Expr
33 | count int
34 | }
35 |
36 | func (a *CountAggState) Copy() AggState {
37 | return &CountAggState{a.alias, a.expr, a.count}
38 | }
39 |
40 | func (a *CountAggState) Init(alias string, expr Expr) error {
41 | a.count = 0
42 | a.expr = expr
43 | a.alias = alias
44 | return nil
45 | }
46 |
47 | func (a *CountAggState) AddTuple(t *Tuple) {
48 | a.count++
49 | }
50 |
51 | func (a *CountAggState) Finalize() *Tuple {
52 | td := a.GetTupleDesc()
53 | f := IntField{int64(a.count)}
54 | fs := []DBValue{f}
55 | t := Tuple{*td, fs, nil}
56 | return &t
57 | }
58 |
59 | func (a *CountAggState) GetTupleDesc() *TupleDesc {
60 | ft := FieldType{a.alias, "", IntType}
61 | fts := []FieldType{ft}
62 | td := TupleDesc{}
63 | td.Fields = fts
64 | return &td
65 | }
66 |
67 | // Implements the aggregation state for SUM
68 | type SumAggState struct {
69 | // TODO: some code goes here
70 | }
71 |
72 | func (a *SumAggState) Copy() AggState {
73 | // TODO: some code goes here
74 | return nil // replace me
75 | }
76 |
77 | func intAggGetter(v DBValue) any {
78 | // TODO: some code goes here
79 | return nil // replace me
80 | }
81 |
82 | func stringAggGetter(v DBValue) any {
83 | // TODO: some code goes here
84 | return nil // replace me
85 | }
86 |
87 | func (a *SumAggState) Init(alias string, expr Expr) error {
88 | // TODO: some code goes here
89 | return fmt.Errorf("SumAggState.Init not implemented") // replace me
90 | }
91 |
92 | func (a *SumAggState) AddTuple(t *Tuple) {
93 | // TODO: some code goes here
94 | }
95 |
96 | func (a *SumAggState) GetTupleDesc() *TupleDesc {
97 | // TODO: some code goes here
98 | return &TupleDesc{} // replace me
99 | }
100 |
101 | func (a *SumAggState) Finalize() *Tuple {
102 | // TODO: some code goes here
103 | return &Tuple{} // replace me
104 | }
105 |
106 | // Implements the aggregation state for AVG
107 | // Note that we always AddTuple() at least once before Finalize()
108 | // so no worries for divide-by-zero
109 | type AvgAggState struct {
110 | // TODO: some code goes here
111 | }
112 |
113 | func (a *AvgAggState) Copy() AggState {
114 | // TODO: some code goes here
115 | return nil // replace me
116 | }
117 |
118 | func (a *AvgAggState) Init(alias string, expr Expr) error {
119 | // TODO: some code goes here
120 | return fmt.Errorf("AvgAggState.Init not implemented") // replace me
121 | }
122 |
123 | func (a *AvgAggState) AddTuple(t *Tuple) {
124 | // TODO: some code goes here
125 | }
126 |
127 | func (a *AvgAggState) GetTupleDesc() *TupleDesc {
128 | // TODO: some code goes here
129 | return &TupleDesc{} // replace me
130 | }
131 |
132 | func (a *AvgAggState) Finalize() *Tuple {
133 | // TODO: some code goes here
134 | return &Tuple{} // replace me
135 | }
136 |
137 | // Implements the aggregation state for MAX
138 | // Note that we always AddTuple() at least once before Finalize()
139 | // so no worries for NaN max
140 | type MaxAggState struct {
141 | // TODO: some code goes here
142 | }
143 |
144 | func (a *MaxAggState) Copy() AggState {
145 | // TODO: some code goes here
146 | return nil // replace me
147 | }
148 |
149 | func (a *MaxAggState) Init(alias string, expr Expr) error {
150 | // TODO: some code goes here
151 | return fmt.Errorf("MaxAggState.Init not implemented") // replace me
152 | }
153 |
154 | func (a *MaxAggState) AddTuple(t *Tuple) {
155 | // TODO: some code goes here
156 | }
157 |
158 | func (a *MaxAggState) GetTupleDesc() *TupleDesc {
159 | // TODO: some code goes here
160 | return &TupleDesc{} // replace me
161 | }
162 |
163 | func (a *MaxAggState) Finalize() *Tuple {
164 | // TODO: some code goes here
165 | return &Tuple{} // replace me
166 | }
167 |
168 | // Implements the aggregation state for MIN
169 | // Note that we always AddTuple() at least once before Finalize()
170 | // so no worries for NaN min
171 | type MinAggState struct {
172 | // TODO: some code goes here
173 | }
174 |
175 | func (a *MinAggState) Copy() AggState {
176 | // TODO: some code goes here
177 | return nil // replace me
178 | }
179 |
180 | func (a *MinAggState) Init(alias string, expr Expr) error {
181 | // TODO: some code goes here
182 | return fmt.Errorf("MinAggState.Init not implemented") // replace me
183 | }
184 |
185 | func (a *MinAggState) AddTuple(t *Tuple) {
186 | // TODO: some code goes here
187 | }
188 |
189 | func (a *MinAggState) GetTupleDesc() *TupleDesc {
190 | // TODO: some code goes here
191 | return &TupleDesc{} // replace me
192 | }
193 |
194 | func (a *MinAggState) Finalize() *Tuple {
195 | // TODO: some code goes here
196 | return &Tuple{} // replace me
197 | }
198 |
--------------------------------------------------------------------------------
/godb/big_join_catalog.txt:
--------------------------------------------------------------------------------
1 | jointest1(name int)
2 | jointest2(name int)
3 |
--------------------------------------------------------------------------------
/godb/buffer_pool.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | //BufferPool provides methods to cache pages that have been read from disk.
4 | //It has a fixed capacity to limit the total amount of memory used by GoDB.
5 | //It is also the primary way in which transactions are enforced, by using page
6 | //level locking (you will not need to worry about this until lab3).
7 |
8 | import (
9 | "fmt"
10 | )
11 |
12 | // Permissions used to when reading / locking pages
13 | type RWPerm int
14 |
15 | const (
16 | ReadPerm RWPerm = iota
17 | WritePerm RWPerm = iota
18 | )
19 |
20 | type BufferPool struct {
21 | // TODO: some code goes here
22 | }
23 |
24 | // Create a new BufferPool with the specified number of pages
25 | func NewBufferPool(numPages int) (*BufferPool, error) {
26 | return &BufferPool{}, fmt.Errorf("NewBufferPool not implemented")
27 | }
28 |
29 | // Testing method -- iterate through all pages in the buffer pool
30 | // and flush them using [DBFile.flushPage]. Does not need to be thread/transaction safe.
31 | // Mark pages as not dirty after flushing them.
32 | func (bp *BufferPool) FlushAllPages() {
33 | // TODO: some code goes here
34 | }
35 |
36 | // Abort the transaction, releasing locks. Because GoDB is FORCE/NO STEAL, none
37 | // of the pages tid has dirtied will be on disk so it is sufficient to just
38 | // release locks to abort. You do not need to implement this for lab 1.
39 | func (bp *BufferPool) AbortTransaction(tid TransactionID) {
40 | // TODO: some code goes here
41 | }
42 |
43 | // Commit the transaction, releasing locks. Because GoDB is FORCE/NO STEAL, none
44 | // of the pages tid has dirtied will be on disk, so prior to releasing locks you
45 | // should iterate through pages and write them to disk. In GoDB lab3 we assume
46 | // that the system will not crash while doing this, allowing us to avoid using a
47 | // WAL. You do not need to implement this for lab 1.
48 | func (bp *BufferPool) CommitTransaction(tid TransactionID) {
49 | // TODO: some code goes here
50 | }
51 |
52 | // Begin a new transaction. You do not need to implement this for lab 1.
53 | //
54 | // Returns an error if the transaction is already running.
55 | func (bp *BufferPool) BeginTransaction(tid TransactionID) error {
56 | // TODO: some code goes here
57 | return nil
58 | }
59 |
60 | // Retrieve the specified page from the specified DBFile (e.g., a HeapFile), on
61 | // behalf of the specified transaction. If a page is not cached in the buffer pool,
62 | // you can read it from disk uing [DBFile.readPage]. If the buffer pool is full (i.e.,
63 | // already stores numPages pages), a page should be evicted. Should not evict
64 | // pages that are dirty, as this would violate NO STEAL. If the buffer pool is
65 | // full of dirty pages, you should return an error. Before returning the page,
66 | // attempt to lock it with the specified permission. If the lock is
67 | // unavailable, should block until the lock is free. If a deadlock occurs, abort
68 | // one of the transactions in the deadlock. For lab 1, you do not need to
69 | // implement locking or deadlock detection. You will likely want to store a list
70 | // of pages in the BufferPool in a map keyed by the [DBFile.pageKey].
71 | func (bp *BufferPool) GetPage(file DBFile, pageNo int, tid TransactionID, perm RWPerm) (Page, error) {
72 | return nil, fmt.Errorf("GetPage not implemented")
73 | }
74 |
--------------------------------------------------------------------------------
/godb/buffer_pool_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "os"
5 | "testing"
6 | )
7 |
8 | func TestBufferPoolGetPage(t *testing.T) {
9 | _, t1, t2, hf, bp, _ := makeTestVars(t)
10 | tid := NewTID()
11 | for i := 0; i < 300; i++ {
12 | bp.BeginTransaction(tid)
13 | err := hf.insertTuple(&t1, tid)
14 | if err != nil {
15 | t.Fatalf("%v", err)
16 | }
17 | err = hf.insertTuple(&t2, tid)
18 | if err != nil {
19 | t.Fatalf("%v", err)
20 | }
21 |
22 | // Force dirty pages to disk. CommitTransaction may not be implemented
23 | // yet if this is called in lab 1 or 2.
24 | bp.FlushAllPages()
25 |
26 | // commit transaction
27 | bp.CommitTransaction(tid)
28 | }
29 | bp.BeginTransaction(tid)
30 | //expect 6 pages
31 | for i := 0; i < 6; i++ {
32 | pg, err := bp.GetPage(hf, i, tid, ReadPerm)
33 | if pg == nil || err != nil {
34 | t.Fatalf("failed to get page %d (err = %v)", i, err)
35 | }
36 | }
37 | _, err := bp.GetPage(hf, 7, tid, ReadPerm)
38 | if err == nil {
39 | t.Fatalf("No error when getting page 7 from a file with 6 pages.")
40 | }
41 | }
42 |
43 | func TestSetDirty(t *testing.T) {
44 | _, t1, _, hf, bp, _ := makeTestVars(t)
45 | tid := NewTID()
46 | bp.BeginTransaction(tid)
47 | for i := 0; i < 308; i++ {
48 | err := hf.insertTuple(&t1, tid)
49 | if err != nil && (i == 306 || i == 307) {
50 | return
51 | } else if err != nil {
52 | t.Fatalf("%v", err)
53 | }
54 | }
55 | bp.CommitTransaction(tid)
56 | t.Fatalf("Expected error due to all pages in BufferPool being dirty")
57 | }
58 |
59 | // Test is only valid up to Lab 4. In Lab 5 we switch from FORCE/NOSTEAL to NOFORCE/STEAL.
60 | func TestBufferPoolHoldsMultipleHeapFiles(t *testing.T) {
61 | if os.Getenv("LAB") == "5" {
62 | t.Skip("This test is only valid up to Lab 4. Skipping")
63 | }
64 |
65 | td, t1, t2, hf, bp, tid := makeTestVars(t)
66 | os.Remove(TestingFile2)
67 | hf2, err := NewHeapFile(TestingFile2, &td, bp)
68 | if err != nil {
69 | print("ERROR MAKING TEST VARS, BLARGH")
70 | panic(err)
71 | }
72 |
73 | err1 := hf.insertTuple(&t1, tid)
74 | err2 := hf.insertTuple(&t1, tid)
75 | err3 := hf2.insertTuple(&t2, tid)
76 |
77 | if err1 != nil || err2 != nil || err3 != nil {
78 | t.Errorf("The BufferPool should be able to handle multiple files")
79 | }
80 | // bp contains 2 dirty pages at this point
81 |
82 | hf2TupCntPerPage := 0
83 | for hf2.NumPages() <= 1 {
84 | if err := hf2.insertTuple(&t2, tid); err != nil {
85 | t.Errorf("%v", err)
86 | }
87 | hf2TupCntPerPage++
88 | }
89 | // bp contains 3 dirty pages at this point
90 |
91 | for i := 0; i < hf2TupCntPerPage-1; i++ {
92 | if err := hf2.insertTuple(&t2, tid); err != nil {
93 | t.Errorf("%v", err)
94 | }
95 | }
96 |
97 | // bp contains 3 dirty pages at this point, including 2 full pages of hf2
98 | _ = hf2.insertTuple(&t2, tid)
99 | if err := hf2.insertTuple(&t2, tid); err == nil {
100 | t.Errorf("should cause bufferpool dirty page overflow here")
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/godb/catalog.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "log"
7 | "os"
8 | "sort"
9 | "strings"
10 | )
11 |
12 | type Table struct {
13 | id int
14 | name string
15 | desc TupleDesc
16 |
17 | // statistics
18 | stats *TableStats
19 |
20 | file DBFile
21 | }
22 |
23 | type Catalog struct {
24 | tableMap map[string]*Table
25 | columnMap map[string][]*Table
26 | bufferPool *BufferPool
27 | rootPath string
28 | filePath string
29 | }
30 |
31 | func (c *Catalog) SaveToFile(catalogFile string, rootPath string) error {
32 | f, err := os.OpenFile(rootPath+"/"+catalogFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
33 | if err != nil {
34 | return err
35 | }
36 | f.WriteString(c.String())
37 | f.Close()
38 | return nil
39 | }
40 |
41 | func (c *Catalog) dropTable(tableName string) error {
42 | _, ok := c.tableMap[tableName]
43 | if !ok {
44 | return GoDBError{NoSuchTableError, "couldn't find table to drop"}
45 | }
46 |
47 | delete(c.tableMap, tableName)
48 | for cn, ts := range c.columnMap {
49 | tsFiltered := make([]*Table, 0)
50 | for _, t := range ts {
51 | if t.name != tableName {
52 | tsFiltered = append(tsFiltered, t)
53 | }
54 | }
55 | c.columnMap[cn] = tsFiltered
56 | }
57 | return nil
58 | }
59 |
60 | func ImportCatalogFromCSVs(
61 | catalogFile string,
62 | bp *BufferPool,
63 | rootPath string,
64 | tableSuffix string,
65 | separator string) error {
66 | c, err := NewCatalogFromFile(catalogFile, bp, rootPath)
67 | if err != nil {
68 | return err
69 | }
70 | for _, t := range c.tableMap {
71 | fileName := rootPath + "/" + t.name + "." + tableSuffix
72 | log.Printf("Loading %s from %s...\n", t.name, fileName)
73 | hf, err := NewHeapFile(c.tableNameToFile(t.name), t.desc.copy(), c.bufferPool)
74 | if err != nil {
75 | return err
76 | }
77 | f, err := os.Open(fileName)
78 | if err != nil {
79 | return err
80 | }
81 | err = hf.LoadFromCSV(f, false, separator, true)
82 | if err != nil {
83 | return err
84 | }
85 | }
86 | return nil
87 | }
88 |
89 | func (c *Catalog) parseCatalogFile() error {
90 | f, err := os.Open(c.rootPath + "/" + c.filePath)
91 | if err != nil {
92 | return err
93 | }
94 | scanner := bufio.NewScanner(f)
95 |
96 | for scanner.Scan() {
97 | // code to read each line
98 | line := strings.ToLower(scanner.Text())
99 | sep := strings.Split(line, "(")
100 | if len(sep) != 2 {
101 | return GoDBError{ParseError, fmt.Sprintf("expected one paren in catalog entry, got %d (%s)", len(sep), line)}
102 | }
103 | tableName := strings.TrimSpace(sep[0])
104 | rest := strings.Trim(sep[1], "()")
105 | fields := strings.Split(rest, ",")
106 |
107 | var fieldArray []FieldType
108 | for _, f := range fields {
109 | f := strings.TrimSpace(f)
110 | nameType := strings.Split(f, " ")
111 | if len(nameType) < 2 || len(nameType) > 4 {
112 | return GoDBError{ParseError, fmt.Sprintf("malformed catalog entry %s (line %s)", nameType, line)}
113 | }
114 |
115 | name := nameType[0]
116 | fieldType := FieldType{name, "", IntType}
117 | switch nameType[1] {
118 | case "int":
119 | fallthrough
120 | case "integer":
121 | fieldType.Ftype = IntType
122 | case "string":
123 | fallthrough
124 | case "varchar":
125 | fallthrough
126 | case "text":
127 | fieldType.Ftype = StringType
128 | default:
129 | return GoDBError{ParseError, fmt.Sprintf("unknown type %s (line %s)", nameType[1], line)}
130 | }
131 | fieldArray = append(fieldArray, fieldType)
132 | }
133 |
134 | _, err := c.addTable(tableName, TupleDesc{fieldArray})
135 | if err != nil {
136 | return err
137 | }
138 | }
139 | return nil
140 | }
141 |
142 | func NewCatalog(catalogFile string, bp *BufferPool, rootPath string) *Catalog {
143 | return &Catalog{make(map[string]*Table), make(map[string][]*Table), bp, rootPath, catalogFile}
144 | }
145 |
146 | func NewCatalogFromFile(catalogFile string, bp *BufferPool, rootPath string) (*Catalog, error) {
147 | c := NewCatalog(catalogFile, bp, rootPath)
148 | if err := c.parseCatalogFile(); err != nil {
149 | return nil, err
150 | }
151 | return c, nil
152 | }
153 |
154 | // Add a new table to the catalog.
155 | //
156 | // Returns an error if the table already exists.
157 | func (c *Catalog) addTable(named string, desc TupleDesc) (DBFile, error) {
158 | f, err := c.GetTable(named)
159 | if err == nil {
160 | return f, GoDBError{DuplicateTableError, fmt.Sprintf("a table named '%s' already exists", named)}
161 | }
162 |
163 | hf, err := NewHeapFile(c.tableNameToFile(named), &desc, c.bufferPool)
164 | if err != nil {
165 | return nil, err
166 | }
167 |
168 | t := &Table{len(c.tableMap), named, desc, nil, hf}
169 | c.tableMap[named] = t
170 | for _, f := range desc.Fields {
171 | mapList := c.columnMap[f.Fname]
172 | if mapList == nil {
173 | mapList = make([]*Table, 0)
174 | }
175 | c.columnMap[f.Fname] = append(mapList, t)
176 | }
177 |
178 | return hf, nil
179 | }
180 |
181 | func (c *Catalog) ComputeTableStats() error {
182 | // Dummy implementation, do not worry about it.
183 | return nil
184 | }
185 |
186 | func (c *Catalog) tableNameToFile(tableName string) string {
187 | return c.rootPath + "/" + tableName + ".dat"
188 | }
189 |
190 | func (c *Catalog) GetTableInfo(named string) (*Table, error) {
191 | t, ok := c.tableMap[named]
192 | if !ok {
193 | return nil, GoDBError{NoSuchTableError, fmt.Sprintf("no table '%s' found", named)}
194 | }
195 | return t, nil
196 | }
197 |
198 | func (c *Catalog) GetTable(named string) (DBFile, error) {
199 | t, err := c.GetTableInfo(named)
200 | if err != nil {
201 | return nil, err
202 | }
203 | return t.file, nil
204 | }
205 |
206 | func (c *Catalog) GetTableInfoId(id int) (*Table, error) {
207 | for _, t := range c.tableMap {
208 | if t.id == id {
209 | return t, nil
210 | }
211 | }
212 | return nil, GoDBError{NoSuchTableError, fmt.Sprintf("no table '%d' found", id)}
213 | }
214 |
215 | func (c *Catalog) GetTableInfoDBFile(f DBFile) (*Table, error) {
216 | for _, t := range c.tableMap {
217 | if t.file == f {
218 | return t, nil
219 | }
220 | }
221 | return nil, GoDBError{NoSuchTableError, "table not found"}
222 | }
223 |
224 | // Get the statistics for a table.
225 | //
226 | // Returns nil if the table does not exist.
227 | func (c *Catalog) GetTableStats(named string) *TableStats {
228 | t, err := c.GetTableInfo(named)
229 | if err != nil {
230 | return nil
231 | }
232 | return t.stats
233 | }
234 |
235 | func (c *Catalog) findTablesWithColumn(named string) []*Table {
236 | return c.columnMap[named]
237 | }
238 |
239 | func (c *Catalog) NumTables() int {
240 | return len(c.tableMap)
241 | }
242 |
243 | func (t *Table) String() string {
244 | var buf strings.Builder
245 | buf.WriteString(t.name)
246 | buf.WriteByte('(')
247 | for i, f := range t.desc.Fields {
248 | if i != 0 {
249 | buf.WriteString(", ")
250 | }
251 | buf.WriteString(f.Fname)
252 | buf.WriteByte(' ')
253 | buf.WriteString(f.Ftype.String())
254 | }
255 | buf.WriteString(")\n")
256 | return buf.String()
257 | }
258 |
259 | func (c *Catalog) String() string {
260 | var buf strings.Builder
261 | keys := make([]string, 0, len(c.tableMap))
262 | for k := range c.tableMap {
263 | keys = append(keys, k)
264 | }
265 | sort.Strings(keys)
266 | for _, t := range keys {
267 | buf.WriteString(c.tableMap[t].String())
268 | }
269 | return buf.String()
270 | }
271 |
272 | func (c *Catalog) CatalogString() string {
273 | return c.String()
274 | }
275 |
--------------------------------------------------------------------------------
/godb/catalog.txt:
--------------------------------------------------------------------------------
1 | t (name string, age int)
2 | t2 (name string, age int)
3 |
--------------------------------------------------------------------------------
/godb/deadlock_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 | "testing"
7 | "time"
8 | )
9 |
10 | const POLL_INTERVAL = 100 * time.Millisecond
11 | const WAIT_INTERVAL = 200 * time.Millisecond
12 |
13 | /**
14 | * Not-so-unit test to construct a deadlock situation.
15 | * t1 acquires p0.read; t2 acquires p1.read; t1 attempts p1.write; t2
16 | * attempts p0.write. Rinse and repeat.
17 | */
18 | func TestDeadlockReadWrite(t *testing.T) {
19 | bp, hf, tid1, tid2 := lockingTestSetUp(t)
20 |
21 | lg1Read := startGrabber(bp, tid1, hf, 0, ReadPerm)
22 | lg2Read := startGrabber(bp, tid2, hf, 1, ReadPerm)
23 |
24 | time.Sleep(POLL_INTERVAL)
25 |
26 | lg1Write := startGrabber(bp, tid1, hf, 1, WritePerm)
27 | lg2Write := startGrabber(bp, tid2, hf, 0, WritePerm)
28 |
29 | for {
30 | time.Sleep(POLL_INTERVAL)
31 |
32 | if lg1Write.acquired() && lg2Write.acquired() {
33 | t.Errorf("Should not both get write lock")
34 | }
35 | if lg1Write.acquired() != lg2Write.acquired() {
36 | break
37 | }
38 |
39 | if lg1Write.getError() != nil {
40 | bp.AbortTransaction(tid1) // at most abort twice; should be able to abort twice
41 | time.Sleep(time.Duration((float64(WAIT_INTERVAL) * rand.Float64())))
42 |
43 | tid1 = NewTID()
44 | lg1Read = startGrabber(bp, tid1, hf, 0, ReadPerm)
45 | time.Sleep(POLL_INTERVAL)
46 | lg1Write = startGrabber(bp, tid1, hf, 1, WritePerm)
47 | }
48 |
49 | if lg2Write.getError() != nil {
50 | bp.AbortTransaction(tid2) // at most abort twice; should be able to abort twice
51 | time.Sleep(time.Duration((float64(WAIT_INTERVAL) * rand.Float64())))
52 |
53 | tid2 = NewTID()
54 | lg2Read = startGrabber(bp, tid2, hf, 1, ReadPerm)
55 | time.Sleep(POLL_INTERVAL)
56 | lg2Write = startGrabber(bp, tid2, hf, 0, WritePerm)
57 | }
58 | }
59 |
60 | if lg1Read == nil || lg2Read == nil {
61 | fmt.Println("should not be nil")
62 | }
63 | }
64 |
65 | /**
66 | * Not-so-unit test to construct a deadlock situation.
67 | * t1 acquires p0.write; t2 acquires p1.write; t1 attempts p1.write; t2
68 | * attempts p0.write.
69 | */
70 | func TestDeadlockWriteWrite(t *testing.T) {
71 | bp, hf, tid1, tid2 := lockingTestSetUp(t)
72 |
73 | lg1WriteA := startGrabber(bp, tid1, hf, 0, WritePerm)
74 | lg2WriteA := startGrabber(bp, tid2, hf, 1, WritePerm)
75 |
76 | time.Sleep(POLL_INTERVAL)
77 |
78 | lg1WriteB := startGrabber(bp, tid1, hf, 1, WritePerm)
79 | lg2WriteB := startGrabber(bp, tid2, hf, 0, WritePerm)
80 |
81 | for {
82 | time.Sleep(POLL_INTERVAL)
83 |
84 | if lg1WriteB.acquired() && lg2WriteB.acquired() {
85 | t.Errorf("Should not both get write lock")
86 | }
87 | if lg1WriteB.acquired() != lg2WriteB.acquired() {
88 | break
89 | }
90 |
91 | if lg1WriteB.getError() != nil {
92 | bp.AbortTransaction(tid1) // at most abort twice; should be able to abort twice
93 | time.Sleep(time.Duration((float64(WAIT_INTERVAL) * rand.Float64())))
94 |
95 | tid1 = NewTID()
96 | lg1WriteA = startGrabber(bp, tid1, hf, 0, WritePerm)
97 | time.Sleep(POLL_INTERVAL)
98 | lg1WriteB = startGrabber(bp, tid1, hf, 1, WritePerm)
99 | }
100 |
101 | if lg2WriteB.getError() != nil {
102 | bp.AbortTransaction(tid2) // at most abort twice; should be able to abort twice
103 | time.Sleep(time.Duration((float64(WAIT_INTERVAL) * rand.Float64())))
104 |
105 | tid2 = NewTID()
106 | lg2WriteA = startGrabber(bp, tid2, hf, 1, WritePerm)
107 | time.Sleep(POLL_INTERVAL)
108 | lg2WriteB = startGrabber(bp, tid2, hf, 0, WritePerm)
109 | }
110 | }
111 |
112 | if lg1WriteA == nil || lg2WriteA == nil {
113 | fmt.Println("should not be nil")
114 | }
115 | }
116 |
117 | /**
118 | * Not-so-unit test to construct a deadlock situation.
119 | * t1 acquires p0.read; t2 acquires p0.read; t1 attempts to upgrade to
120 | * p0.write; t2 attempts to upgrade to p0.write
121 | */
122 | func TestDeadlockUpgradeWrite(t *testing.T) {
123 | bp, hf, tid1, tid2 := lockingTestSetUp(t)
124 |
125 | lg1Read := startGrabber(bp, tid1, hf, 0, ReadPerm)
126 | lg2Read := startGrabber(bp, tid2, hf, 0, ReadPerm)
127 |
128 | time.Sleep(POLL_INTERVAL)
129 |
130 | lg1Write := startGrabber(bp, tid1, hf, 0, WritePerm)
131 | lg2Write := startGrabber(bp, tid2, hf, 0, WritePerm)
132 |
133 | for {
134 | time.Sleep(POLL_INTERVAL)
135 |
136 | if lg1Write.acquired() && lg2Write.acquired() {
137 | t.Errorf("Should not both get write lock")
138 | }
139 | if lg1Write.acquired() != lg2Write.acquired() {
140 | break
141 | }
142 |
143 | if lg1Write.getError() != nil {
144 | bp.AbortTransaction(tid1) // at most abort twice; should be able to abort twice
145 | time.Sleep(time.Duration((float64(WAIT_INTERVAL) * rand.Float64())))
146 |
147 | tid1 = NewTID()
148 | lg1Read = startGrabber(bp, tid1, hf, 0, ReadPerm)
149 | time.Sleep(POLL_INTERVAL)
150 | lg1Write = startGrabber(bp, tid1, hf, 0, WritePerm)
151 | }
152 |
153 | if lg2Write.getError() != nil {
154 | bp.AbortTransaction(tid2) // at most abort twice; should be able to abort twice
155 | time.Sleep(time.Duration((float64(WAIT_INTERVAL) * rand.Float64())))
156 |
157 | tid2 = NewTID()
158 | lg2Read = startGrabber(bp, tid2, hf, 0, ReadPerm)
159 | time.Sleep(POLL_INTERVAL)
160 | lg2Write = startGrabber(bp, tid2, hf, 0, WritePerm)
161 | }
162 | }
163 |
164 | if lg1Read == nil || lg2Read == nil {
165 | fmt.Println("should not be nil")
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/godb/delete_op.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | type DeleteOp struct {
8 | // TODO: some code goes here
9 | }
10 |
11 | // Construct a delete operator. The delete operator deletes the records in the
12 | // child Operator from the specified DBFile.
13 | func NewDeleteOp(deleteFile DBFile, child Operator) *DeleteOp {
14 | // TODO: some code goes here
15 | return nil // replace me
16 | }
17 |
18 | // The delete TupleDesc is a one column descriptor with an integer field named
19 | // "count".
20 | func (i *DeleteOp) Descriptor() *TupleDesc {
21 | // TODO: some code goes here
22 | return &TupleDesc{} // replace me
23 |
24 | }
25 |
26 | // Return an iterator that deletes all of the tuples from the child iterator
27 | // from the DBFile passed to the constructor and then returns a one-field tuple
28 | // with a "count" field indicating the number of tuples that were deleted.
29 | // Tuples should be deleted using the [DBFile.deleteTuple] method.
30 | func (dop *DeleteOp) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
31 | // TODO: some code goes here
32 | return nil, fmt.Errorf("DeleteOp.Iterator not implemented") // replace me
33 | }
34 |
--------------------------------------------------------------------------------
/godb/delete_op_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | // This function is for _testing only_! It is not part of the godb API.
8 | func BeginTransactionForTest(t *testing.T, bp *BufferPool) TransactionID {
9 | t.Helper()
10 | tid := NewTID()
11 | err := bp.BeginTransaction(tid)
12 | if err != nil {
13 | t.Fatalf(err.Error())
14 | }
15 | return tid
16 | }
17 |
18 | func TestDelete(t *testing.T) {
19 | _, t1, t2, hf, bp, tid := makeTestVars(t)
20 |
21 | insertTupleForTest(t, hf, &t1, tid)
22 | insertTupleForTest(t, hf, &t2, tid)
23 |
24 | bp.CommitTransaction(tid)
25 | var f FieldType = FieldType{"age", "", IntType}
26 | filt, err := NewFilter(&ConstExpr{IntField{25}, IntType}, OpGt, &FieldExpr{f}, hf)
27 | if err != nil {
28 | t.Errorf(err.Error())
29 | }
30 | dop := NewDeleteOp(hf, filt)
31 | if dop == nil {
32 | t.Fatalf("delete op was nil")
33 | }
34 |
35 | tid = BeginTransactionForTest(t, bp)
36 | iter, _ := dop.Iterator(tid)
37 | if iter == nil {
38 | t.Fatalf("iter was nil")
39 | }
40 | tup, err := iter()
41 | if err != nil {
42 | t.Fatalf(err.Error())
43 | }
44 | if tup == nil {
45 | t.Fatalf("insert did not return tuple")
46 | }
47 | intField, ok := tup.Fields[0].(IntField)
48 | if !ok || len(tup.Fields) != 1 || intField.Value != 1 {
49 | t.Fatalf("invalid output tuple")
50 | }
51 | bp.CommitTransaction(tid)
52 |
53 | tid = BeginTransactionForTest(t, bp)
54 |
55 | iter, _ = hf.Iterator(tid)
56 |
57 | cnt := 0
58 | for {
59 | tup, _ := iter()
60 | if tup == nil {
61 | break
62 | }
63 | cnt++
64 | }
65 | if cnt != 1 {
66 | t.Errorf("unexpected number of results after deletion")
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/godb/easy_parser_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "testing"
7 | )
8 |
9 | type Query struct {
10 | SQL string
11 | Ordered bool
12 | }
13 |
14 | func TestParseEasy(t *testing.T) {
15 | queries := []Query{
16 | {SQL: "select sum(age) as s from t group by t.name having s > 30", Ordered: false},
17 | {SQL: "select sum(age + 10) , sum(age) from t", Ordered: false},
18 | {SQL: "select min(age) + max(age) from t", Ordered: false},
19 | {SQL: "select * from t order by t.age, t.name limit 1+2", Ordered: true},
20 | {SQL: "select t.name, t.age from t join t2 on t.name = t2.name, t2 as t3 where t.age < 50 and t3.age = t.age order by t.age asc, t.name asc", Ordered: true},
21 | {SQL: "select sq(sq(5)) from t", Ordered: false},
22 | {SQL: "select 1, name from t", Ordered: false},
23 | {SQL: "select age, name from t", Ordered: false},
24 | {SQL: "select t.name, sum(age) totage from t group by t.name", Ordered: false},
25 | {SQL: "select t.name, t.age from t join t2 on t.name = t2.name where t.age < 50", Ordered: false},
26 | {SQL: "select name from (select x.name from (select t.name from t) x)y order by name asc", Ordered: true},
27 | {SQL: "select age, count(*) from t group by age", Ordered: false},
28 | }
29 | save := false //set save to true to save the output of the current test run as the correct answer
30 | printOutput := false //print the result set during testing
31 |
32 | bp, c, err := MakeParserTestDatabase(10)
33 | if err != nil {
34 | t.Fatalf("failed to create test database, %s", err.Error())
35 | }
36 |
37 | qNo := 0
38 | for _, query := range queries {
39 | tid := BeginTransactionForTest(t, bp)
40 | qNo++
41 |
42 | qType, plan, err := Parse(c, query.SQL)
43 | if err != nil {
44 | t.Fatalf("failed to parse, q=%s, %s", query.SQL, err.Error())
45 | }
46 | if plan == nil {
47 | t.Fatalf("plan was nil")
48 | }
49 | if qType != IteratorType {
50 | continue
51 | }
52 |
53 | var outfile *HeapFile
54 | var outfile_csv *os.File
55 | var resultSet []*Tuple
56 | fname := fmt.Sprintf("savedresults/q%d-easy-result.csv", qNo)
57 |
58 | if save {
59 | os.Remove(fname)
60 | outfile_csv, err = os.OpenFile(fname, os.O_RDWR|os.O_CREATE, 0644)
61 | if err != nil {
62 | t.Fatalf("failed to open CSV file (%s)", err.Error())
63 | }
64 | //outfile, _ = NewHeapFile(fname, plan.Descriptor(), bp)
65 | } else {
66 | fname_bin := fmt.Sprintf("savedresults/q%d-easy-result.dat", qNo)
67 | os.Remove(fname_bin)
68 | desc := plan.Descriptor()
69 | if desc == nil {
70 | t.Fatalf("descriptor was nil")
71 | }
72 |
73 | outfile, _ = NewHeapFile(fname_bin, desc, bp)
74 | if outfile == nil {
75 | t.Fatalf("heapfile was nil")
76 | }
77 | f, err := os.Open(fname)
78 | if err != nil {
79 | t.Fatalf("csv file with results was nil (%s)", err.Error())
80 | }
81 | err = outfile.LoadFromCSV(f, true, ",", false)
82 | if err != nil {
83 | t.Fatalf(err.Error())
84 | }
85 |
86 | resultIter, err := outfile.Iterator(tid)
87 | if err != nil {
88 | t.Fatalf(err.Error())
89 | }
90 | for {
91 | tup, err := resultIter()
92 | if err != nil {
93 | t.Fatalf(err.Error())
94 | }
95 |
96 | if tup != nil {
97 | resultSet = append(resultSet, tup)
98 | } else {
99 | break
100 | }
101 | }
102 | }
103 |
104 | if printOutput || save {
105 | fmt.Printf("Doing %s\n", query.SQL)
106 | iter, err := plan.Iterator(tid)
107 | if err != nil {
108 | t.Fatalf("%s", err.Error())
109 |
110 | }
111 | nresults := 0
112 | if save {
113 | fmt.Fprintf(outfile_csv, "%s\n", plan.Descriptor().HeaderString(false))
114 | }
115 | fmt.Printf("%s\n", plan.Descriptor().HeaderString(true))
116 | for {
117 | tup, err := iter()
118 | if err != nil {
119 | t.Errorf("%s", err.Error())
120 | break
121 | }
122 | if tup == nil {
123 | break
124 | } else {
125 | fmt.Printf("%s\n", tup.PrettyPrintString(true))
126 | }
127 | nresults++
128 | if save {
129 | fmt.Fprintf(outfile_csv, "%s\n", tup.PrettyPrintString(false))
130 | //outfile.insertTuple(tup, tid)
131 | }
132 | }
133 | fmt.Printf("(%d results)\n\n", nresults)
134 | }
135 | if save {
136 | bp.FlushAllPages()
137 | outfile.bufPool.CommitTransaction(tid)
138 | outfile_csv.Close()
139 | } else {
140 | iter, err := plan.Iterator(tid)
141 | if err != nil {
142 | t.Fatalf("%s", err.Error())
143 | }
144 | if query.Ordered {
145 | err = CheckIfOutputMatches(iter, resultSet)
146 | } else {
147 | err = CheckIfOutputMatchesUnordered(iter, resultSet)
148 | }
149 | if err != nil {
150 | t.Errorf("query '%s' did not match expected result set: %v", query.SQL, err)
151 | verbose := true
152 | if verbose {
153 | fmt.Print("Expected: \n")
154 | for _, r := range resultSet {
155 | fmt.Printf("%s\n", r.PrettyPrintString(true))
156 | }
157 | }
158 | }
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/godb/exprs.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 | "time"
7 | )
8 |
9 | //Expressions can be applied to tuples to get concrete values. They
10 | //encapsulate constates, simple fields, and functions over multiple other
11 | //expressions. We have provided the expression methods for you; you will need
12 | //use the [EvalExpr] method in your operator implementations to get fields and
13 | //other values from tuples.
14 |
15 | type Expr interface {
16 | EvalExpr(t *Tuple) (DBValue, error) //DBValue is either IntField or StringField
17 | GetExprType() FieldType //Return the type of the Expression
18 | }
19 |
20 | type FieldExpr struct {
21 | selectField FieldType
22 | }
23 |
24 | func (f *FieldExpr) EvalExpr(t *Tuple) (DBValue, error) {
25 | outTup, err := t.project([]FieldType{f.selectField})
26 | if err != nil {
27 | //fmt.Printf("err in project: %s", err.Error())
28 | return nil, err
29 | }
30 | return outTup.Fields[0], nil
31 |
32 | }
33 |
34 | func (f *FieldExpr) GetExprType() FieldType {
35 | return f.selectField
36 | }
37 |
38 | type ConstExpr struct {
39 | val DBValue
40 | constType DBType
41 | }
42 |
43 | func (c *ConstExpr) GetExprType() FieldType {
44 | return FieldType{"const", fmt.Sprintf("%v", c.val), c.constType}
45 | }
46 |
47 | func (c *ConstExpr) EvalExpr(_ *Tuple) (DBValue, error) {
48 | return c.val, nil
49 | }
50 |
51 | type FuncExpr struct {
52 | op string
53 | args []*Expr
54 | }
55 |
56 | func (f *FuncExpr) GetExprType() FieldType {
57 | fType, exists := funcs[f.op]
58 | //todo return err
59 | if !exists {
60 | return FieldType{f.op, "", IntType}
61 | }
62 | ft := FieldType{f.op, "", IntType}
63 | for _, fe := range f.args {
64 | fieldExpr, ok := (*fe).(*FieldExpr)
65 | if ok {
66 | ft = fieldExpr.GetExprType()
67 | }
68 | }
69 | return FieldType{ft.Fname, ft.TableQualifier, fType.outType}
70 |
71 | }
72 |
73 | type FuncType struct {
74 | argTypes []DBType
75 | outType DBType
76 | f func([]any) any
77 | }
78 |
79 | var funcs = map[string]FuncType{
80 | //note should all be lower case
81 | "+": {[]DBType{IntType, IntType}, IntType, addFunc},
82 | "-": {[]DBType{IntType, IntType}, IntType, minusFunc},
83 | "*": {[]DBType{IntType, IntType}, IntType, timesFunc},
84 | "/": {[]DBType{IntType, IntType}, IntType, divFunc},
85 | "mod": {[]DBType{IntType, IntType}, IntType, modFunc},
86 | "rand": {[]DBType{}, IntType, randIntFunc},
87 | "sq": {[]DBType{IntType}, IntType, sqFunc},
88 | "getsubstr": {[]DBType{StringType, IntType, IntType}, StringType, subStrFunc},
89 | "epoch": {[]DBType{}, IntType, epoch},
90 | "datetimestringtoepoch": {[]DBType{StringType}, IntType, dateTimeToEpoch},
91 | "datestringtoepoch": {[]DBType{StringType}, IntType, dateToEpoch},
92 | "epochtodatetimestring": {[]DBType{IntType}, StringType, dateString},
93 | "imin": {[]DBType{IntType, IntType}, IntType, minFunc},
94 | "imax": {[]DBType{IntType, IntType}, IntType, maxFunc},
95 | }
96 |
97 | func ListOfFunctions() string {
98 | fList := ""
99 | for name, f := range funcs {
100 | args := "("
101 | argList := f.argTypes
102 | hasArg := false
103 | for _, a := range argList {
104 | if hasArg {
105 | args = args + ","
106 | }
107 | switch a {
108 | case IntType:
109 | args = args + "int"
110 | case StringType:
111 | args = args + "string"
112 | }
113 | hasArg = true
114 | }
115 | args = args + ")"
116 | fList = fList + "\t" + name + args + "\n"
117 | }
118 | return fList
119 | }
120 | func minFunc(args []any) any {
121 | first := args[0].(int64)
122 | second := args[1].(int64)
123 | if first < second {
124 | return first
125 | }
126 | return second
127 | }
128 |
129 | func maxFunc(args []any) any {
130 | first := args[0].(int64)
131 | second := args[1].(int64)
132 | if first >= second {
133 | return first
134 | }
135 | return second
136 | }
137 |
138 | func dateTimeToEpoch(args []any) any {
139 | inString := args[0].(string)
140 | tt, err := time.Parse(time.UnixDate, inString)
141 | if err != nil {
142 | return int64(0)
143 | }
144 | return int64(time.Time.Unix(tt))
145 | }
146 |
147 | func dateToEpoch(args []any) any {
148 | inString := args[0].(string)
149 | tt, err := time.Parse("2006-01-02", inString)
150 | if err != nil {
151 | return int64(0)
152 | }
153 | return int64(time.Time.Unix(tt))
154 | }
155 |
156 | func dateString(args []any) any {
157 | unixTime := args[0].(int64)
158 | t := time.Unix(unixTime, 0)
159 | strDate := t.Format(time.UnixDate)
160 | return strDate
161 | }
162 |
163 | func epoch(args []any) any {
164 | t := time.Now()
165 | return time.Time.Unix(t)
166 | }
167 |
168 | func randIntFunc(args []any) any {
169 | return int64(rand.Int())
170 | }
171 |
172 | func modFunc(args []any) any {
173 | return args[0].(int64) % args[1].(int64)
174 | }
175 |
176 | func divFunc(args []any) any {
177 | return args[0].(int64) / args[1].(int64)
178 | }
179 |
180 | func timesFunc(args []any) any {
181 | return args[0].(int64) * args[1].(int64)
182 | }
183 |
184 | func minusFunc(args []any) any {
185 | return args[0].(int64) - args[1].(int64)
186 | }
187 |
188 | func addFunc(args []any) any {
189 | return args[0].(int64) + args[1].(int64)
190 | }
191 |
192 | func sqFunc(args []any) any {
193 | return args[0].(int64) * args[0].(int64)
194 | }
195 |
196 | func subStrFunc(args []any) any {
197 | stringVal := args[0].(string)
198 | start := args[1].(int64)
199 | numChars := args[2].(int64)
200 |
201 | var substr string
202 | if start < 0 || start > int64(len(stringVal)) {
203 | substr = ""
204 | } else if start+numChars > int64(len(stringVal)) {
205 | substr = stringVal[start:]
206 | } else {
207 | substr = stringVal[start : start+numChars]
208 | }
209 |
210 | return substr
211 | }
212 |
213 | func (f *FuncExpr) EvalExpr(t *Tuple) (DBValue, error) {
214 | fType, exists := funcs[f.op]
215 | if !exists {
216 | return nil, GoDBError{ParseError, fmt.Sprintf("unknown function %s", f.op)}
217 | }
218 | if len(f.args) != len(fType.argTypes) {
219 | return nil, GoDBError{ParseError, fmt.Sprintf("function %s expected %d args", f.op, len(fType.argTypes))}
220 | }
221 | argvals := make([]any, len(fType.argTypes))
222 | for i, argType := range fType.argTypes {
223 | arg := *f.args[i]
224 | if arg.GetExprType().Ftype != argType {
225 | typeName := "string"
226 | switch argType {
227 | case IntType:
228 | typeName = "int"
229 | }
230 | return nil, GoDBError{ParseError, fmt.Sprintf("function %s expected arg of type %s", f.op, typeName)}
231 | }
232 | val, err := arg.EvalExpr(t)
233 | if err != nil {
234 | return nil, err
235 | }
236 | switch argType {
237 | case IntType:
238 | argvals[i] = val.(IntField).Value
239 | case StringType:
240 | argvals[i] = val.(StringField).Value
241 | }
242 | }
243 | result := fType.f(argvals)
244 | switch fType.outType {
245 | case IntType:
246 | return IntField{result.(int64)}, nil
247 | case StringType:
248 | return StringField{result.(string)}, nil
249 | }
250 | return nil, GoDBError{ParseError, "unknown result type in function"}
251 | }
252 |
--------------------------------------------------------------------------------
/godb/filter_op.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | type Filter struct {
8 | op BoolOp
9 | left Expr
10 | right Expr
11 | child Operator
12 | }
13 |
14 | // Construct a filter operator on ints.
15 | func NewFilter(constExpr Expr, op BoolOp, field Expr, child Operator) (*Filter, error) {
16 | return &Filter{op, field, constExpr, child}, nil
17 | }
18 |
19 | // Return a TupleDescriptor for this filter op.
20 | func (f *Filter) Descriptor() *TupleDesc {
21 | // TODO: some code goes here
22 | return &TupleDesc{} // replace me
23 | }
24 |
25 | // Filter operator implementation. This function should iterate over the results
26 | // of the child iterator and return a tuple if it satisfies the predicate.
27 | //
28 | // HINT: you can use [types.evalPred] to compare two values.
29 | func (f *Filter) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
30 | // TODO: some code goes here
31 | return nil, fmt.Errorf("Filter.Iterator not implemented") // replace me
32 | }
33 |
--------------------------------------------------------------------------------
/godb/filter_op_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | // This function is for _testing only_! It is not part of the godb API.
8 | func insertTupleForTest(t *testing.T, hf DBFile, tup *Tuple, tid TransactionID) {
9 | t.Helper()
10 | err := hf.insertTuple(tup, tid)
11 | if err != nil {
12 | t.Fatalf(err.Error())
13 | }
14 | }
15 |
16 | func TestFilterInt(t *testing.T) {
17 | _, t1, t2, hf, _, tid := makeTestVars(t)
18 |
19 | insertTupleForTest(t, hf, &t1, tid)
20 | insertTupleForTest(t, hf, &t2, tid)
21 |
22 | var f FieldType = FieldType{"age", "", IntType}
23 | filt, err := NewFilter(&ConstExpr{IntField{25}, IntType}, OpGt, &FieldExpr{f}, hf)
24 | if err != nil {
25 | t.Errorf(err.Error())
26 | }
27 | iter, err := filt.Iterator(tid)
28 | if err != nil {
29 | t.Fatalf(err.Error())
30 | }
31 | if iter == nil {
32 | t.Fatalf("Iterator was nil")
33 | }
34 |
35 | cnt := 0
36 | for {
37 | tup, _ := iter()
38 | if tup == nil {
39 | break
40 | }
41 | t.Logf("filter passed tup %d: %v\n", cnt, tup)
42 | cnt++
43 | }
44 | if cnt != 1 {
45 | t.Errorf("unexpected number of results")
46 | }
47 | }
48 |
49 | func TestFilterString(t *testing.T) {
50 | _, t1, t2, hf, _, tid := makeTestVars(t)
51 | insertTupleForTest(t, hf, &t1, tid)
52 | insertTupleForTest(t, hf, &t2, tid)
53 | var f FieldType = FieldType{"name", "", StringType}
54 | filt, err := NewFilter(&ConstExpr{StringField{"sam"}, StringType}, OpEq, &FieldExpr{f}, hf)
55 | if err != nil {
56 | t.Errorf(err.Error())
57 | }
58 | iter, err := filt.Iterator(tid)
59 | if err != nil {
60 | t.Fatalf(err.Error())
61 | }
62 | if iter == nil {
63 | t.Fatalf("Iterator was nil")
64 | }
65 |
66 | cnt := 0
67 | for {
68 | tup, _ := iter()
69 | if tup == nil {
70 | break
71 | }
72 | t.Logf("filter passed tup %d: %v\n", cnt, tup)
73 | cnt++
74 | }
75 | if cnt != 1 {
76 | t.Errorf("unexpected number of results")
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/godb/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/srmadden/godb
2 |
3 | go 1.21
4 |
5 | require github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
6 |
7 | require (
8 | github.com/d4l3k/messagediff v1.2.1 // indirect
9 | github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43
10 | )
11 |
--------------------------------------------------------------------------------
/godb/go.sum:
--------------------------------------------------------------------------------
1 | github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
2 | github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43/go.mod h1:OYRfF6eb5wY9VRFkXJH8FFBi3plw2v+giaIu7P054pM=
3 | github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ=
4 | github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
5 |
--------------------------------------------------------------------------------
/godb/godberrorcode_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=GoDBErrorCode"; DO NOT EDIT.
2 |
3 | package godb
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[TupleNotFoundError-0]
12 | _ = x[PageFullError-1]
13 | _ = x[IncompatibleTypesError-2]
14 | _ = x[TypeMismatchError-3]
15 | _ = x[MalformedDataError-4]
16 | _ = x[BufferPoolFullError-5]
17 | _ = x[ParseError-6]
18 | _ = x[DuplicateTableError-7]
19 | _ = x[NoSuchTableError-8]
20 | _ = x[AmbiguousNameError-9]
21 | _ = x[IllegalOperationError-10]
22 | _ = x[DeadlockError-11]
23 | _ = x[IllegalTransactionError-12]
24 | }
25 |
26 | const _GoDBErrorCode_name = "TupleNotFoundErrorPageFullErrorIncompatibleTypesErrorTypeMismatchErrorMalformedDataErrorBufferPoolFullErrorParseErrorDuplicateTableErrorNoSuchTableErrorAmbiguousNameErrorIllegalOperationErrorDeadlockErrorIllegalTransactionError"
27 |
28 | var _GoDBErrorCode_index = [...]uint8{0, 18, 31, 53, 70, 88, 107, 117, 136, 152, 170, 191, 204, 227}
29 |
30 | func (i GoDBErrorCode) String() string {
31 | if i < 0 || i >= GoDBErrorCode(len(_GoDBErrorCode_index)-1) {
32 | return "GoDBErrorCode(" + strconv.FormatInt(int64(i), 10) + ")"
33 | }
34 | return _GoDBErrorCode_name[_GoDBErrorCode_index[i]:_GoDBErrorCode_index[i+1]]
35 | }
36 |
--------------------------------------------------------------------------------
/godb/heap_file.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "os"
7 | "strconv"
8 | "strings"
9 | )
10 |
11 | // A HeapFile is an unordered collection of tuples.
12 | //
13 | // HeapFile is a public class because external callers may wish to instantiate
14 | // database tables using the method [LoadFromCSV]
15 | type HeapFile struct {
16 | // TODO: some code goes here
17 | // HeapFile should include the fields below; you may want to add
18 | // additional fields
19 | bufPool *BufferPool
20 | }
21 |
22 | // Create a HeapFile.
23 | // Parameters
24 | // - fromFile: backing file for the HeapFile. May be empty or a previously created heap file.
25 | // - td: the TupleDesc for the HeapFile.
26 | // - bp: the BufferPool that is used to store pages read from the HeapFile
27 | // May return an error if the file cannot be opened or created.
28 | func NewHeapFile(fromFile string, td *TupleDesc, bp *BufferPool) (*HeapFile, error) {
29 | // TODO: some code goes here
30 | return &HeapFile{}, fmt.Errorf("NewHeapFile not implemented") //replace me
31 | }
32 |
33 | // Return the name of the backing file
34 | func (f *HeapFile) BackingFile() string {
35 | // TODO: some code goes here
36 | return "" //replace me
37 | }
38 |
39 | // Return the number of pages in the heap file
40 | func (f *HeapFile) NumPages() int {
41 | // TODO: some code goes here
42 | return 0 //replace me
43 | }
44 |
45 | // Load the contents of a heap file from a specified CSV file. Parameters are as follows:
46 | // - hasHeader: whether or not the CSV file has a header
47 | // - sep: the character to use to separate fields
48 | // - skipLastField: if true, the final field is skipped (some TPC datasets include a trailing separator on each line)
49 | // Returns an error if the field cannot be opened or if a line is malformed
50 | // We provide the implementation of this method, but it won't work until
51 | // [HeapFile.insertTuple] and some other utility functions are implemented
52 | func (f *HeapFile) LoadFromCSV(file *os.File, hasHeader bool, sep string, skipLastField bool) error {
53 | scanner := bufio.NewScanner(file)
54 | cnt := 0
55 | for scanner.Scan() {
56 | line := scanner.Text()
57 | fields := strings.Split(line, sep)
58 | if skipLastField {
59 | fields = fields[0 : len(fields)-1]
60 | }
61 | numFields := len(fields)
62 | cnt++
63 | desc := f.Descriptor()
64 | if desc == nil || desc.Fields == nil {
65 | return GoDBError{MalformedDataError, "Descriptor was nil"}
66 | }
67 | if numFields != len(desc.Fields) {
68 | return GoDBError{MalformedDataError, fmt.Sprintf("LoadFromCSV: line %d (%s) does not have expected number of fields (expected %d, got %d)", cnt, line, len(f.Descriptor().Fields), numFields)}
69 | }
70 | if cnt == 1 && hasHeader {
71 | continue
72 | }
73 | var newFields []DBValue
74 | for fno, field := range fields {
75 | switch f.Descriptor().Fields[fno].Ftype {
76 | case IntType:
77 | field = strings.TrimSpace(field)
78 | floatVal, err := strconv.ParseFloat(field, 64)
79 | if err != nil {
80 | return GoDBError{TypeMismatchError, fmt.Sprintf("LoadFromCSV: couldn't convert value %s to int, tuple %d", field, cnt)}
81 | }
82 | intValue := int(floatVal)
83 | newFields = append(newFields, IntField{int64(intValue)})
84 | case StringType:
85 | if len(field) > StringLength {
86 | field = field[0:StringLength]
87 | }
88 | newFields = append(newFields, StringField{field})
89 | }
90 | }
91 | newT := Tuple{*f.Descriptor(), newFields, nil}
92 | tid := NewTID()
93 | bp := f.bufPool
94 | f.insertTuple(&newT, tid)
95 |
96 | // Force dirty pages to disk. CommitTransaction may not be implemented
97 | // yet if this is called in lab 1 or 2.
98 | bp.FlushAllPages()
99 |
100 | }
101 | return nil
102 | }
103 |
104 | // Read the specified page number from the HeapFile on disk. This method is
105 | // called by the [BufferPool.GetPage] method when it cannot find the page in its
106 | // cache.
107 | //
108 | // This method will need to open the file supplied to the constructor, seek to
109 | // the appropriate offset, read the bytes in, and construct a [heapPage] object,
110 | // using the [heapPage.initFromBuffer] method.
111 | func (f *HeapFile) readPage(pageNo int) (Page, error) {
112 | // TODO: some code goes here
113 | return nil, fmt.Errorf("readPage not implemented")
114 | }
115 |
116 | // Add the tuple to the HeapFile. This method should search through pages in the
117 | // heap file, looking for empty slots and adding the tuple in the first empty
118 | // slot if finds.
119 | //
120 | // If none are found, it should create a new [heapPage] and insert the tuple
121 | // there, and write the heapPage to the end of the HeapFile (e.g., using the
122 | // [flushPage] method.)
123 | //
124 | // To iterate through pages, it should use the [BufferPool.GetPage method]
125 | // rather than directly reading pages itself. For lab 1, you do not need to
126 | // worry about concurrent transactions modifying the Page or HeapFile. We will
127 | // add support for concurrent modifications in lab 3.
128 | //
129 | // The page the tuple is inserted into should be marked as dirty.
130 | func (f *HeapFile) insertTuple(t *Tuple, tid TransactionID) error {
131 | // TODO: some code goes here
132 | return fmt.Errorf("insertTuple not implemented") //replace me
133 | }
134 |
135 | // Remove the provided tuple from the HeapFile.
136 | //
137 | // This method should use the [Tuple.Rid] field of t to determine which tuple to
138 | // remove. The Rid field should be set when the tuple is read using the
139 | // [Iterator] method, or is otherwise created (as in tests). Note that Rid is an
140 | // empty interface, so you can supply any object you wish. You will likely want
141 | // to identify the heap page and slot within the page that the tuple came from.
142 | //
143 | // The page the tuple is deleted from should be marked as dirty.
144 | func (f *HeapFile) deleteTuple(t *Tuple, tid TransactionID) error {
145 | // TODO: some code goes here
146 | return fmt.Errorf("deleteTuple not implemented") //replace me
147 | }
148 |
149 | // Method to force the specified page back to the backing file at the
150 | // appropriate location. This will be called by BufferPool when it wants to
151 | // evict a page. The Page object should store information about its offset on
152 | // disk (e.g., that it is the ith page in the heap file), so you can determine
153 | // where to write it back.
154 | func (f *HeapFile) flushPage(p Page) error {
155 | // TODO: some code goes here
156 | return fmt.Errorf("flushPage not implemented") //replace me
157 | }
158 |
159 | // [Operator] descriptor method -- return the TupleDesc for this HeapFile
160 | // Supplied as argument to NewHeapFile.
161 | func (f *HeapFile) Descriptor() *TupleDesc {
162 | // TODO: some code goes here
163 | return nil //replace me
164 |
165 | }
166 |
167 | // [Operator] iterator method
168 | // Return a function that iterates through the records in the heap file
169 | // Note that this method should read pages from the HeapFile using the
170 | // BufferPool method GetPage, rather than reading pages directly,
171 | // since the BufferPool caches pages and manages page-level locking state for
172 | // transactions
173 | // You should esnure that Tuples returned by this method have their Rid object
174 | // set appropriate so that [deleteTuple] will work (see additional comments there).
175 | // Make sure to set the returned tuple's TupleDescriptor to the TupleDescriptor of
176 | // the HeapFile. This allows it to correctly capture the table qualifier.
177 | func (f *HeapFile) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
178 | // TODO: some code goes here
179 | return func() (*Tuple, error) {
180 | return nil, fmt.Errorf("heap_file.Iterator not implemented")
181 | }, nil
182 | }
183 |
184 | // internal strucuture to use as key for a heap page
185 | type heapHash struct {
186 | FileName string
187 | PageNo int
188 | }
189 |
190 | // This method returns a key for a page to use in a map object, used by
191 | // BufferPool to determine if a page is cached or not. We recommend using a
192 | // heapHash struct as the key for a page, although you can use any struct that
193 | // does not contain a slice or a map that uniquely identifies the page.
194 | func (f *HeapFile) pageKey(pgNo int) any {
195 | // TODO: some code goes here
196 | return nil
197 | }
198 |
--------------------------------------------------------------------------------
/godb/heap_file_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "os"
5 | "testing"
6 | )
7 |
8 | const TestingFile string = "test.dat"
9 | const TestingFile2 string = "test2.dat"
10 |
11 | func makeTestFile(t *testing.T, bufferPoolSize int) (*BufferPool, *HeapFile) {
12 | os.Remove(TestingFile)
13 |
14 | bp, c, err := MakeTestDatabase(bufferPoolSize, "catalog.txt")
15 | if err != nil {
16 | t.Fatalf(err.Error())
17 | }
18 |
19 | td, _, _ := makeTupleTestVars()
20 | tbl, err := c.addTable("test", td)
21 | if err != nil {
22 | t.Fatalf(err.Error())
23 | }
24 | return bp, tbl.(*HeapFile)
25 | }
26 |
27 | func makeTestVars(t *testing.T) (TupleDesc, Tuple, Tuple, *HeapFile, *BufferPool, TransactionID) {
28 | bp, hf := makeTestFile(t, 3)
29 | td, t1, t2 := makeTupleTestVars()
30 | tid := NewTID()
31 | bp.BeginTransaction(tid)
32 | return td, t1, t2, hf, bp, tid
33 | }
34 |
35 | func TestHeapFileCreateAndInsert(t *testing.T) {
36 | _, t1, t2, hf, _, tid := makeTestVars(t)
37 | err := hf.insertTuple(&t1, tid)
38 |
39 | hf.insertTuple(&t2, tid)
40 | iter, err := hf.Iterator(tid)
41 | if err != nil {
42 | t.Fatalf(err.Error())
43 | }
44 |
45 | i := 0
46 | for {
47 | tup, err := iter()
48 | if err != nil {
49 | t.Fatalf(err.Error())
50 | }
51 |
52 | if tup == nil {
53 | break
54 | }
55 | i = i + 1
56 | }
57 | if i != 2 {
58 | t.Fatalf("HeapFile iterator expected 2 tuples, got %d", i)
59 | }
60 | }
61 |
62 | func TestHeapFileDelete(t *testing.T) {
63 | _, t1, t2, hf, _, tid := makeTestVars(t)
64 | err := hf.insertTuple(&t1, tid)
65 | if err != nil {
66 | t.Fatalf(err.Error())
67 | }
68 |
69 | err = hf.insertTuple(&t2, tid)
70 | if err != nil {
71 | t.Fatalf(err.Error())
72 | }
73 |
74 | err = hf.deleteTuple(&t1, tid)
75 | if err != nil {
76 | t.Fatalf(err.Error())
77 | }
78 |
79 | iter, err := hf.Iterator(tid)
80 | if err != nil {
81 | t.Fatalf(err.Error())
82 | }
83 |
84 | t3, err := iter()
85 | if err != nil {
86 | t.Fatalf(err.Error())
87 | }
88 | if t3 == nil {
89 | t.Fatalf("HeapFile iterator expected 1 tuple")
90 | }
91 |
92 | err = hf.deleteTuple(&t2, tid)
93 | if err != nil {
94 | t.Fatalf(err.Error())
95 | }
96 |
97 | iter, err = hf.Iterator(tid)
98 | if err != nil {
99 | t.Fatalf(err.Error())
100 | }
101 |
102 | t3, err = iter()
103 | if err != nil {
104 | t.Fatalf(err.Error())
105 | }
106 |
107 | if t3 != nil {
108 | t.Fatalf("HeapFile iterator expected 0 tuple")
109 | }
110 | }
111 |
112 | func testSerializeN(t *testing.T, n int) {
113 | bp, hf := makeTestFile(t, max(1, n/50))
114 | _, t1, t2 := makeTupleTestVars()
115 |
116 | tid := NewTID()
117 | bp.BeginTransaction(tid)
118 | for i := 0; i < n; i++ {
119 | if err := hf.insertTuple(&t1, tid); err != nil {
120 | t.Fatalf(err.Error())
121 | }
122 |
123 | if err := hf.insertTuple(&t2, tid); err != nil {
124 | t.Fatalf(err.Error())
125 | }
126 | }
127 | bp.CommitTransaction(tid)
128 | bp.FlushAllPages()
129 |
130 | bp2, catalog, err := MakeTestDatabase(1, "catalog.txt")
131 | if err != nil {
132 | t.Fatalf(err.Error())
133 | }
134 | hf2, err := catalog.addTable("test", *hf.Descriptor())
135 | if err != nil {
136 | t.Fatalf(err.Error())
137 | }
138 |
139 | tid = NewTID()
140 | bp2.BeginTransaction(tid)
141 |
142 | iter, err := hf2.Iterator(tid)
143 | if err != nil {
144 | t.Fatalf(err.Error())
145 | }
146 |
147 | i := 0
148 | for tup, err := iter(); tup != nil; tup, err = iter() {
149 | if err != nil {
150 | t.Fatalf(err.Error())
151 | }
152 | i = i + 1
153 | }
154 | if i != 2*n {
155 | t.Fatalf("HeapFile iterator expected %d tuples, got %d", 2*n, i)
156 | }
157 |
158 | }
159 | func TestHeapFileSerializeSmall(t *testing.T) {
160 | testSerializeN(t, 2)
161 | }
162 |
163 | func TestHeapFileSerializeLarge(t *testing.T) {
164 | testSerializeN(t, 2000)
165 | }
166 |
167 | func TestHeapFileSerializeVeryLarge(t *testing.T) {
168 | testSerializeN(t, 4000)
169 | }
170 |
171 | func TestHeapFileLoadCSV(t *testing.T) {
172 | _, _, _, hf, _, tid := makeTestVars(t)
173 | f, err := os.Open("test_heap_file.csv")
174 | if err != nil {
175 | t.Fatalf("Couldn't open test_heap_file.csv")
176 | }
177 | err = hf.LoadFromCSV(f, true, ",", false)
178 | if err != nil {
179 | t.Fatalf("Load failed, %s", err)
180 | }
181 | //should have 384 records
182 | iter, _ := hf.Iterator(tid)
183 | i := 0
184 | for {
185 | t, _ := iter()
186 | if t == nil {
187 | break
188 | }
189 | i = i + 1
190 | }
191 | if i != 384 {
192 | t.Fatalf("HeapFile iterator expected 384 tuples, got %d", i)
193 | }
194 | }
195 |
196 | func TestHeapFilePageKey(t *testing.T) {
197 | td, t1, _, hf, bp, tid := makeTestVars(t)
198 |
199 | os.Remove(TestingFile2)
200 | hf2, err := NewHeapFile(TestingFile2, &td, bp)
201 | if err != nil {
202 | t.Fatalf(err.Error())
203 | }
204 |
205 | for hf.NumPages() < 2 {
206 | err = hf.insertTuple(&t1, tid)
207 | if err != nil {
208 | t.Fatalf(err.Error())
209 | }
210 |
211 | err = hf2.insertTuple(&t1, tid)
212 | if err != nil {
213 | t.Fatalf(err.Error())
214 | }
215 |
216 | if hf.NumPages() == 0 {
217 | t.Fatalf("Heap file should have at least one page after insertion.")
218 | }
219 |
220 | bp.FlushAllPages()
221 | }
222 |
223 | if hf.NumPages() != hf2.NumPages() || hf.NumPages() != 2 {
224 | t.Fatalf("Should be two pages here")
225 | }
226 |
227 | for i := 0; i < hf.NumPages(); i++ {
228 | if hf.pageKey(i) != hf.pageKey(i) {
229 | t.Fatalf("Expected equal pageKey")
230 | }
231 | if hf.pageKey(i) == hf.pageKey((i+1)%hf.NumPages()) {
232 | t.Fatalf("Expected non-equal pageKey for different pages")
233 | }
234 | if hf.pageKey(i) == hf2.pageKey(i) {
235 | t.Fatalf("Expected non-equal pageKey for different heapfiles")
236 | }
237 | }
238 | }
239 |
240 | func TestHeapFileSize(t *testing.T) {
241 | _, t1, _, hf, bp, _ := makeTestVars(t)
242 |
243 | tid := NewTID()
244 | bp.BeginTransaction(tid)
245 | hf.insertTuple(&t1, tid)
246 | page, err := bp.GetPage(hf, 0, tid, ReadPerm)
247 | if err != nil {
248 | t.Fatalf("unexpected error, getPage, %s", err.Error())
249 | }
250 | hf.flushPage(page)
251 | info, err := os.Stat(TestingFile)
252 | if err != nil {
253 | t.Fatalf("unexpected error, stat, %s", err.Error())
254 | }
255 | if info.Size() != int64(PageSize) {
256 | t.Fatalf("heap file page is not %d bytes; NOTE: This error may be OK, but many implementations that don't write full pages break.", PageSize)
257 | }
258 | }
259 |
260 | func TestHeapFileSetDirty(t *testing.T) {
261 | if os.Getenv("LAB") == "5" {
262 | t.Skip("This test is only valid up to Lab 4. Skipping")
263 | }
264 |
265 | _, t1, _, hf, bp, tid := makeTestVars(t)
266 | for i := 0; i < 308; i++ {
267 | err := hf.insertTuple(&t1, tid)
268 | if err != nil && (i == 306 || i == 307) {
269 | return
270 | } else if err != nil {
271 | t.Fatalf("%v", err)
272 | }
273 | }
274 | bp.CommitTransaction(tid)
275 | t.Fatalf("Expected error due to all pages in BufferPool being dirty")
276 | }
277 |
278 | func TestHeapFileDirtyBit(t *testing.T) {
279 | _, t1, _, hf, bp, _ := makeTestVars(t)
280 |
281 | tid := NewTID()
282 | bp.BeginTransaction(tid)
283 | hf.insertTuple(&t1, tid)
284 | hf.insertTuple(&t1, tid)
285 | page, _ := bp.GetPage(hf, 0, tid, ReadPerm)
286 | if !page.isDirty() {
287 | t.Fatalf("Expected page to be dirty")
288 | }
289 | }
290 |
291 | func TestHeapFileIteratorExtra(t *testing.T) {
292 | _, t1, _, hf, bp, _ := makeTestVars(t)
293 | tid := NewTID()
294 | bp.BeginTransaction(tid)
295 |
296 | it, err := hf.Iterator(tid)
297 | _, err = it()
298 | if err != nil {
299 | t.Fatalf("Empty heap file iterator should return nil,nil")
300 | }
301 | hf.insertTuple(&t1, tid)
302 | it, err = hf.Iterator(tid)
303 | pg, err := it()
304 | if err != nil {
305 | t.Fatalf("Iterating over heap file with one tuple returned error %s", err.Error())
306 | }
307 | if pg == nil {
308 | t.Fatalf("Should have gotten 1 page in heap file iterator")
309 | }
310 | pg, err = it()
311 | if pg != nil {
312 | t.Fatalf("More than 1 page in heap file iterator!")
313 | }
314 | if err != nil {
315 | t.Fatalf("Iterator returned error at end, expected nil, nil, got nil, %s", err.Error())
316 | }
317 | }
318 |
--------------------------------------------------------------------------------
/godb/heap_page.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | )
7 |
8 | /* HeapPage implements the Page interface for pages of HeapFiles. We have
9 | provided our interface to HeapPage below for you to fill in, but you are not
10 | required to implement these methods except for the three methods that the Page
11 | interface requires. You will want to use an interface like what we provide to
12 | implement the methods of [HeapFile] that insert, delete, and iterate through
13 | tuples.
14 |
15 | In GoDB all tuples are fixed length, which means that given a TupleDesc it is
16 | possible to figure out how many tuple "slots" fit on a given page.
17 |
18 | In addition, all pages are PageSize bytes. They begin with a header with a 32
19 | bit integer with the number of slots (tuples), and a second 32 bit integer with
20 | the number of used slots.
21 |
22 | Each tuple occupies the same number of bytes. You can use the go function
23 | unsafe.Sizeof() to determine the size in bytes of an object. So, a GoDB integer
24 | (represented as an int64) requires unsafe.Sizeof(int64(0)) bytes. For strings,
25 | we encode them as byte arrays of StringLength, so they are size
26 | ((int)(unsafe.Sizeof(byte('a')))) * StringLength bytes. The size in bytes of a
27 | tuple is just the sum of the size in bytes of its fields.
28 |
29 | Once you have figured out how big a record is, you can determine the number of
30 | slots on on the page as:
31 |
32 | remPageSize = PageSize - 8 // bytes after header
33 | numSlots = remPageSize / bytesPerTuple //integer division will round down
34 |
35 | To serialize a page to a buffer, you can then:
36 |
37 | write the number of slots as an int32
38 | write the number of used slots as an int32
39 | write the tuples themselves to the buffer
40 |
41 | You will follow the inverse process to read pages from a buffer.
42 |
43 | Note that to process deletions you will likely delete tuples at a specific
44 | position (slot) in the heap page. This means that after a page is read from
45 | disk, tuples should retain the same slot number. Because GoDB will never evict a
46 | dirty page, it's OK if tuples are renumbered when they are written back to disk.
47 |
48 | */
49 |
50 | type heapPage struct {
51 | // TODO: some code goes here
52 | }
53 |
54 | // Construct a new heap page
55 | func newHeapPage(desc *TupleDesc, pageNo int, f *HeapFile) (*heapPage, error) {
56 | // TODO: some code goes here
57 | return &heapPage{}, fmt.Errorf("newHeapPage is not implemented") //replace me
58 | }
59 |
60 | func (h *heapPage) getNumSlots() int {
61 | // TODO: some code goes here
62 | return 0 //replace me
63 | }
64 |
65 | // Insert the tuple into a free slot on the page, or return an error if there are
66 | // no free slots. Set the tuples rid and return it.
67 | func (h *heapPage) insertTuple(t *Tuple) (recordID, error) {
68 | // TODO: some code goes here
69 | return 0, fmt.Errorf("insertTuple not implemented") //replace me
70 | }
71 |
72 | // Delete the tuple at the specified record ID, or return an error if the ID is
73 | // invalid.
74 | func (h *heapPage) deleteTuple(rid recordID) error {
75 | // TODO: some code goes here
76 | return fmt.Errorf("deleteTuple not implemented") //replace me
77 | }
78 |
79 | // Page method - return whether or not the page is dirty
80 | func (h *heapPage) isDirty() bool {
81 | // TODO: some code goes here
82 | return false //replace me
83 | }
84 |
85 | // Page method - mark the page as dirty
86 | func (h *heapPage) setDirty(tid TransactionID, dirty bool) {
87 | // TODO: some code goes here
88 | }
89 |
90 | // Page method - return the corresponding HeapFile
91 | // for this page.
92 | func (p *heapPage) getFile() DBFile {
93 | // TODO: some code goes here
94 | return nil //replace me
95 | }
96 |
97 | // Allocate a new bytes.Buffer and write the heap page to it. Returns an error
98 | // if the write to the the buffer fails. You will likely want to call this from
99 | // your [HeapFile.flushPage] method. You should write the page header, using
100 | // the binary.Write method in LittleEndian order, followed by the tuples of the
101 | // page, written using the Tuple.writeTo method.
102 | func (h *heapPage) toBuffer() (*bytes.Buffer, error) {
103 | // TODO: some code goes here
104 | return nil, fmt.Errorf("heap_page.toBuffer not implemented") //replace me
105 | }
106 |
107 | // Read the contents of the HeapPage from the supplied buffer.
108 | func (h *heapPage) initFromBuffer(buf *bytes.Buffer) error {
109 | // TODO: some code goes here
110 | return fmt.Errorf("initFromBuffer not implemented") //replace me
111 | }
112 |
113 | // Return a function that iterates through the tuples of the heap page. Be sure
114 | // to set the rid of the tuple to the rid struct of your choosing beforing
115 | // return it. Return nil, nil when the last tuple is reached.
116 | func (p *heapPage) tupleIter() func() (*Tuple, error) {
117 | // TODO: some code goes here
118 | return func() (*Tuple, error) {
119 | return nil, fmt.Errorf("heap_file.Iterator not implemented") // replace me
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/godb/heap_page_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "testing"
5 | "unsafe"
6 | )
7 |
8 | func TestHeapPageInsert(t *testing.T) {
9 | td, t1, t2, hf, _, _ := makeTestVars(t)
10 | pg, err := newHeapPage(&td, 0, hf)
11 | if err != nil {
12 | t.Fatalf(err.Error())
13 | }
14 | var expectedSlots = (PageSize - 8) / (StringLength + int(unsafe.Sizeof(int64(0))))
15 | if pg.getNumSlots() != expectedSlots {
16 | t.Fatalf("Incorrect number of slots, expected %d, got %d", expectedSlots, pg.getNumSlots())
17 | }
18 |
19 | _, err = pg.insertTuple(&t1)
20 | if err != nil {
21 | t.Fatalf(err.Error())
22 | }
23 |
24 | _, err = pg.insertTuple(&t2)
25 | if err != nil {
26 | t.Fatalf(err.Error())
27 | }
28 |
29 | iter := pg.tupleIter()
30 | if iter == nil {
31 | t.Fatalf("Iterator was nil")
32 | }
33 |
34 | cnt := 0
35 | for {
36 | tup, err := iter()
37 | if err != nil {
38 | t.Fatalf(err.Error())
39 | }
40 | if tup == nil {
41 | break
42 | }
43 |
44 | cnt += 1
45 | }
46 | if cnt != 2 {
47 | t.Errorf("Expected 2 tuples in interator, got %d", cnt)
48 | }
49 | }
50 |
51 | func TestHeapPageDelete(t *testing.T) {
52 | td, t1, t2, hf, _, _ := makeTestVars(t)
53 | pg, err := newHeapPage(&td, 0, hf)
54 | if err != nil {
55 | t.Fatalf(err.Error())
56 | }
57 |
58 | pg.insertTuple(&t1)
59 | slotNo, _ := pg.insertTuple(&t2)
60 | pg.deleteTuple(slotNo)
61 |
62 | iter := pg.tupleIter()
63 | if iter == nil {
64 | t.Fatalf("Iterator was nil")
65 | }
66 | cnt := 0
67 | for {
68 |
69 | tup, _ := iter()
70 | if tup == nil {
71 | break
72 | }
73 |
74 | cnt += 1
75 | }
76 | if cnt != 1 {
77 | t.Errorf("Expected 1 tuple in interator, got %d", cnt)
78 | }
79 | }
80 |
81 | // Unit test for insertTuple
82 | func TestHeapPageInsertTuple(t *testing.T) {
83 | td, t1, _, hf, _, _ := makeTestVars(t)
84 | page, err := newHeapPage(&td, 0, hf)
85 | if err != nil {
86 | t.Fatalf(err.Error())
87 | }
88 | free := page.getNumSlots()
89 |
90 | for i := 0; i < free; i++ {
91 | var addition = Tuple{
92 | Desc: td,
93 | Fields: []DBValue{
94 | StringField{"sam"},
95 | IntField{int64(i)},
96 | },
97 | }
98 | page.insertTuple(&addition)
99 |
100 | iter := page.tupleIter()
101 | if iter == nil {
102 | t.Fatalf("Iterator was nil")
103 | }
104 | cnt, found := 0, false
105 | for {
106 |
107 | tup, _ := iter()
108 | found = found || addition.equals(tup)
109 | if tup == nil {
110 | break
111 | }
112 |
113 | cnt += 1
114 | }
115 | if cnt != i+1 {
116 | t.Errorf("Expected %d tuple in interator, got %d", i+1, cnt)
117 | }
118 | if !found {
119 | t.Errorf("Expected inserted tuple to be FOUND, got NOT FOUND")
120 | }
121 | }
122 |
123 | _, err = page.insertTuple(&t1)
124 |
125 | if err == nil {
126 | t.Errorf("Expected error due to full page")
127 | }
128 | }
129 |
130 | // Unit test for deleteTuple
131 | func TestHeapPageDeleteTuple(t *testing.T) {
132 | td, _, _, hf, _, _ := makeTestVars(t)
133 | page, err := newHeapPage(&td, 0, hf)
134 | if err != nil {
135 | t.Fatalf(err.Error())
136 | }
137 | free := page.getNumSlots()
138 |
139 | list := make([]recordID, free)
140 | for i := 0; i < free; i++ {
141 | var addition = Tuple{
142 | Desc: td,
143 | Fields: []DBValue{
144 | StringField{"sam"},
145 | IntField{int64(i)},
146 | },
147 | }
148 | list[i], _ = page.insertTuple(&addition)
149 | }
150 | if len(list) == 0 {
151 | t.Fatalf("Rid list is empty.")
152 | }
153 | for i, rnd := free-1, 0xdefaced; i > 0; i, rnd = i-1, (rnd*0x7deface1+12354)%0x7deface9 {
154 | // Generate a random index j such that 0 <= j <= i.
155 | j := rnd % (i + 1)
156 |
157 | // Swap arr[i] and arr[j].
158 | list[i], list[j] = list[j], list[i]
159 | }
160 |
161 | for _, rid := range list {
162 | err := page.deleteTuple(rid)
163 | if err != nil {
164 | t.Errorf("Found error %s", err.Error())
165 | }
166 | }
167 |
168 | err = page.deleteTuple(list[0])
169 | if err == nil {
170 | t.Errorf("page should be empty; expected error")
171 | }
172 | }
173 |
174 | // Unit test for isDirty, setDirty
175 | func TestHeapPageDirty(t *testing.T) {
176 | td, _, _, hf, _, _ := makeTestVars(t)
177 | page, err := newHeapPage(&td, 0, hf)
178 | if err != nil {
179 | t.Fatalf(err.Error())
180 | }
181 |
182 | page.setDirty(0, true)
183 | if !page.isDirty() {
184 | t.Errorf("page should be dirty")
185 | }
186 | page.setDirty(0, true)
187 | if !page.isDirty() {
188 | t.Errorf("page should be dirty")
189 | }
190 | page.setDirty(-1, false)
191 | if page.isDirty() {
192 | t.Errorf("page should be not dirty")
193 | }
194 | }
195 |
196 | // Unit test for toBuffer and initFromBuffer
197 | func TestHeapPageSerialization(t *testing.T) {
198 | td, _, _, hf, _, _ := makeTestVars(t)
199 | page, err := newHeapPage(&td, 0, hf)
200 | if err != nil {
201 | t.Fatalf(err.Error())
202 | }
203 | free := page.getNumSlots()
204 |
205 | for i := 0; i < free-1; i++ {
206 | var addition = Tuple{
207 | Desc: td,
208 | Fields: []DBValue{
209 | StringField{"sam"},
210 | IntField{int64(i)},
211 | },
212 | }
213 | page.insertTuple(&addition)
214 | }
215 |
216 | buf, _ := page.toBuffer()
217 | page2, err := newHeapPage(&td, 0, hf)
218 | if err != nil {
219 | t.Fatalf(err.Error())
220 | }
221 | err = page2.initFromBuffer(buf)
222 | if err != nil {
223 | t.Fatalf("Error loading heap page from buffer.")
224 | }
225 |
226 | iter, iter2 := page.tupleIter(), page2.tupleIter()
227 | if iter == nil {
228 | t.Fatalf("iter was nil.")
229 | }
230 | if iter2 == nil {
231 | t.Fatalf("iter2 was nil.")
232 | }
233 |
234 | findEqCount := func(t0 *Tuple, iter3 func() (*Tuple, error)) int {
235 | cnt := 0
236 | for tup, _ := iter3(); tup != nil; tup, _ = iter3() {
237 | if t0.equals(tup) {
238 | cnt += 1
239 | }
240 | }
241 | return cnt
242 | }
243 |
244 | for {
245 | tup, _ := iter()
246 | if tup == nil {
247 | break
248 | }
249 | if findEqCount(tup, page.tupleIter()) != findEqCount(tup, page2.tupleIter()) {
250 | t.Errorf("Serialization / deserialization doesn't result in identical heap page.")
251 | }
252 | }
253 | }
254 |
255 | func TestHeapPageBufferLen(t *testing.T) {
256 | td, _, _, hf, _, _ := makeTestVars(t)
257 | page, err := newHeapPage(&td, 0, hf)
258 | if err != nil {
259 | t.Fatalf(err.Error())
260 | }
261 | free := page.getNumSlots()
262 |
263 | for i := 0; i < free-1; i++ {
264 | var addition = Tuple{
265 | Desc: td,
266 | Fields: []DBValue{
267 | StringField{"sam"},
268 | IntField{int64(i)},
269 | },
270 | }
271 | page.insertTuple(&addition)
272 | }
273 |
274 | buf, _ := page.toBuffer()
275 |
276 | if buf.Len() != PageSize {
277 | t.Fatalf("HeapPage.toBuffer returns buffer of unexpected size; NOTE: This error may be OK, but many implementations that don't write full pages break.")
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/godb/insert_op.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import "fmt"
4 |
5 | type InsertOp struct {
6 | // TODO: some code goes here
7 | }
8 |
9 | // Construct an insert operator that inserts the records in the child Operator
10 | // into the specified DBFile.
11 | func NewInsertOp(insertFile DBFile, child Operator) *InsertOp {
12 | // TODO: some code goes here
13 | return nil
14 | }
15 |
16 | // The insert TupleDesc is a one column descriptor with an integer field named "count"
17 | func (i *InsertOp) Descriptor() *TupleDesc {
18 | // TODO: some code goes here
19 | return nil
20 | }
21 |
22 | // Return an iterator function that inserts all of the tuples from the child
23 | // iterator into the DBFile passed to the constuctor and then returns a
24 | // one-field tuple with a "count" field indicating the number of tuples that
25 | // were inserted. Tuples should be inserted using the [DBFile.insertTuple]
26 | // method.
27 | func (iop *InsertOp) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
28 | // TODO: some code goes here
29 | return nil, fmt.Errorf("InsertOp.Iterator not implemented")
30 | }
31 |
--------------------------------------------------------------------------------
/godb/insert_op_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "os"
5 | "testing"
6 | )
7 |
8 | const InsertTestFile string = "InsertTestFile.dat"
9 |
10 | func TestInsert(t *testing.T) {
11 | td, t1, _, hf, bp, tid := makeTestVars(t)
12 | hf.insertTuple(&t1, tid)
13 | hf.insertTuple(&t1, tid)
14 | bp.CommitTransaction(tid)
15 | os.Remove(InsertTestFile)
16 | hf2, _ := NewHeapFile(InsertTestFile, &td, bp)
17 | if hf2 == nil {
18 | t.Fatalf("hf was nil")
19 | }
20 | tid = NewTID()
21 | bp.BeginTransaction(tid)
22 | ins := NewInsertOp(hf2, hf)
23 | iter, _ := ins.Iterator(tid)
24 | if iter == nil {
25 | t.Fatalf("iter was nil")
26 | }
27 | tup, err := iter()
28 | if err != nil {
29 | t.Errorf(err.Error())
30 | return
31 | }
32 | if tup == nil {
33 | t.Errorf("insert did not return tuple")
34 | return
35 | }
36 | intField, ok := tup.Fields[0].(IntField)
37 | if !ok || len(tup.Fields) != 1 || intField.Value != 2 {
38 | t.Errorf("invalid output tuple")
39 | return
40 | }
41 | bp.CommitTransaction(tid)
42 | tid = NewTID()
43 | bp.BeginTransaction(tid)
44 |
45 | cnt := 0
46 | iter, _ = hf2.Iterator(tid)
47 | for {
48 | tup, err := iter()
49 |
50 | if err != nil {
51 | t.Errorf(err.Error())
52 | }
53 | if tup == nil {
54 | break
55 | }
56 | cnt = cnt + 1
57 | }
58 | if cnt != 2 {
59 | t.Errorf("insert failed, expected 2 tuples, got %d", cnt)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/godb/join_op.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | type EqualityJoin struct {
8 | // Expressions that when applied to tuples from the left or right operators,
9 | // respectively, return the value of the left or right side of the join
10 | leftField, rightField Expr
11 |
12 | left, right *Operator // Operators for the two inputs of the join
13 |
14 | // The maximum number of records of intermediate state that the join should
15 | // use (only required for optional exercise).
16 | maxBufferSize int
17 | }
18 |
19 | // Constructor for a join of integer expressions.
20 | //
21 | // Returns an error if either the left or right expression is not an integer.
22 | func NewJoin(left Operator, leftField Expr, right Operator, rightField Expr, maxBufferSize int) (*EqualityJoin, error) {
23 | return &EqualityJoin{leftField, rightField, &left, &right, maxBufferSize}, nil
24 | }
25 |
26 | // Return a TupleDesc for this join. The returned descriptor should contain the
27 | // union of the fields in the descriptors of the left and right operators.
28 | //
29 | // HINT: use [TupleDesc.merge].
30 | func (hj *EqualityJoin) Descriptor() *TupleDesc {
31 | // TODO: some code goes here
32 | return &TupleDesc{} // replace me
33 | }
34 |
35 | // Join operator implementation. This function should iterate over the results
36 | // of the join. The join should be the result of joining joinOp.left and
37 | // joinOp.right, applying the joinOp.leftField and joinOp.rightField expressions
38 | // to the tuples of the left and right iterators respectively, and joining them
39 | // using an equality predicate.
40 | //
41 | // HINT: When implementing the simple nested loop join, you should keep in mind
42 | // that you only iterate through the left iterator once (outer loop) but iterate
43 | // through the right iterator once for every tuple in the left iterator (inner
44 | // loop).
45 | //
46 | // HINT: You can use [Tuple.joinTuples] to join two tuples.
47 | //
48 | // OPTIONAL EXERCISE: the operator implementation should not use more than
49 | // maxBufferSize records, and should pass the testBigJoin test without timing
50 | // out. To pass this test, you will need to use something other than a nested
51 | // loops join.
52 | func (joinOp *EqualityJoin) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
53 | // TODO: some code goes here
54 | return nil, fmt.Errorf("EqualityJoin.Iterator not implemented") // replace me
55 | }
56 |
--------------------------------------------------------------------------------
/godb/join_op_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "testing"
7 | "time"
8 | )
9 |
10 | const JoinTestFile string = "JoinTestFile.dat"
11 |
12 | func TestJoin(t *testing.T) {
13 | td, t1, t2, hf, bp, tid := makeTestVars(t)
14 | insertTupleForTest(t, hf, &t1, tid)
15 | insertTupleForTest(t, hf, &t2, tid)
16 | insertTupleForTest(t, hf, &t2, tid)
17 |
18 | os.Remove(JoinTestFile)
19 | hf2, _ := NewHeapFile(JoinTestFile, &td, bp)
20 | insertTupleForTest(t, hf2, &t1, tid)
21 | insertTupleForTest(t, hf2, &t2, tid)
22 | insertTupleForTest(t, hf2, &t2, tid)
23 |
24 | outT1 := joinTuples(&t1, &t1)
25 | outT2 := joinTuples(&t2, &t2)
26 |
27 | leftField := FieldExpr{td.Fields[1]}
28 | join, err := NewJoin(hf, &leftField, hf2, &leftField, 100)
29 | if err != nil {
30 | t.Errorf("unexpected error initializing join")
31 | return
32 | }
33 | iter, err := join.Iterator(tid)
34 | if err != nil {
35 | t.Fatalf(err.Error())
36 | }
37 | if iter == nil {
38 | t.Fatalf("iter was nil")
39 | }
40 | cnt := 0
41 | cntOut1 := 0
42 | cntOut2 := 0
43 | for {
44 | t, _ := iter()
45 | if t == nil {
46 | break
47 | }
48 | if t.equals(outT1) {
49 | cntOut1++
50 | } else if t.equals(outT2) {
51 | cntOut2++
52 | }
53 | //fmt.Printf("got tuple %v: %v\n", cnt, t)
54 | cnt++
55 | }
56 | if cnt != 5 {
57 | t.Errorf("unexpected number of join results (%d, expected 5)", cnt)
58 | }
59 | if cntOut1 != 1 {
60 | t.Errorf("unexpected number of t1 results (%d, expected 1)", cntOut1)
61 | }
62 | if cntOut2 != 4 {
63 | t.Errorf("unexpected number of t2 results (%d, expected 4)", cntOut2)
64 | }
65 |
66 | }
67 |
68 | const BigJoinFile1 string = "jointest1.dat"
69 | const BigJoinFile2 string = "jointest2.dat"
70 |
71 | //This test joins two large heap files (each containing ntups tuples). A simple
72 | //nested loops join will take a LONG time to complete this join, so we've added
73 | //a timeout that will cause the join to fail after 10 seconds.
74 | //
75 | //Note that this test is optional; passing it will give extra credit, as
76 | //describe in the lab 2 assignment.
77 |
78 | func TestJoinBigOptional(t *testing.T) {
79 | timeout := time.After(20 * time.Second)
80 |
81 | done := make(chan bool)
82 |
83 | go func() {
84 | fail := func(err error) {
85 | done <- true
86 | t.Errorf(err.Error())
87 | }
88 | ntups := 314159
89 |
90 | if err := os.Remove(BigJoinFile1); err != nil && !os.IsNotExist(err) {
91 | fail(fmt.Errorf("removing file1: %w", err))
92 | return
93 | }
94 | if err := os.Remove(BigJoinFile2); err != nil && !os.IsNotExist(err) {
95 | fail(fmt.Errorf("removing file2: %w", err))
96 | }
97 |
98 | bp, c, err := MakeTestDatabase(100, "big_join_catalog.txt")
99 | if err != nil {
100 | fail(fmt.Errorf("making database: %w", err))
101 | return
102 | }
103 |
104 | hf1, err := c.GetTable("jointest1")
105 | if err != nil {
106 | fail(fmt.Errorf("getting jointest1: %w", err))
107 | return
108 | }
109 | hf2, err := c.GetTable("jointest2")
110 | if err != nil {
111 | fail(fmt.Errorf("getting jointest2: %w", err))
112 | return
113 | }
114 |
115 | tid := NewTID()
116 | bp.BeginTransaction(tid)
117 | for i := 0; i < ntups; i++ {
118 |
119 | if i > 0 && i%5000 == 0 {
120 | bp.FlushAllPages()
121 | // commit transaction
122 | bp.CommitTransaction(tid)
123 |
124 | tid = NewTID()
125 | bp.BeginTransaction(tid)
126 | }
127 |
128 | tup := Tuple{*hf1.Descriptor(), []DBValue{IntField{int64(i)}}, nil}
129 | err := hf1.insertTuple(&tup, tid)
130 | if err != nil {
131 | fail(fmt.Errorf("inserting tuple1: %w", err))
132 | return
133 | }
134 |
135 | err = hf2.insertTuple(&tup, tid)
136 | if err != nil {
137 | fail(fmt.Errorf("inserting tuple2: %w", err))
138 | return
139 | }
140 | }
141 | bp.CommitTransaction(tid)
142 |
143 | tid = NewTID()
144 | bp.BeginTransaction(tid)
145 | leftField := FieldExpr{hf1.Descriptor().Fields[0]}
146 | join, err := NewJoin(hf1, &leftField, hf2, &leftField, 100000)
147 | if err != nil {
148 | t.Errorf("unexpected error initializing join")
149 | done <- true
150 | return
151 | }
152 | iter, err := join.Iterator(tid)
153 | if err != nil {
154 | fail(err)
155 | return
156 | }
157 |
158 | if iter == nil {
159 | t.Errorf("iter was nil")
160 | done <- true
161 | return
162 | }
163 | cnt := 0
164 | for {
165 | tup, err := iter()
166 | if err != nil {
167 | fail(err)
168 | return
169 | }
170 | if tup == nil {
171 | break
172 | }
173 | cnt++
174 | }
175 | if cnt != ntups {
176 | t.Errorf("unexpected number of join results (%d, expected %d)", cnt, ntups)
177 | }
178 | done <- true
179 | }()
180 |
181 | select {
182 | case <-timeout:
183 | t.Fatal("Test didn't finish in time")
184 | case <-done:
185 | }
186 | }
187 |
188 | func makeJoinOrderingVars(t *testing.T) (*HeapFile, *HeapFile, Tuple, Tuple, *BufferPool) {
189 | var td1 = TupleDesc{Fields: []FieldType{
190 | {Fname: "a", Ftype: StringType},
191 | {Fname: "b", Ftype: IntType},
192 | }}
193 | var td2 = TupleDesc{Fields: []FieldType{
194 | {Fname: "c", Ftype: StringType},
195 | {Fname: "d", Ftype: IntType},
196 | }}
197 |
198 | var t1 = Tuple{
199 | Desc: td1,
200 | Fields: []DBValue{
201 | StringField{"sam"},
202 | IntField{25},
203 | }}
204 |
205 | var t2 = Tuple{
206 | Desc: td2,
207 | Fields: []DBValue{
208 | StringField{"george jones"},
209 | IntField{25},
210 | }}
211 |
212 | bp, err := NewBufferPool(3)
213 | if err != nil {
214 | t.Fatalf(err.Error())
215 | }
216 | os.Remove(TestingFile)
217 | hf1, err := NewHeapFile(TestingFile, &td1, bp)
218 | if err != nil {
219 | t.Fatalf(err.Error())
220 | }
221 |
222 | os.Remove(TestingFile2)
223 | hf2, err := NewHeapFile(TestingFile2, &td2, bp)
224 | if err != nil {
225 | t.Fatalf(err.Error())
226 | }
227 |
228 | return hf1, hf2, t1, t2, bp
229 | }
230 |
231 | func TestJoinFieldOrder(t *testing.T) {
232 | bp, c, err := MakeTestDatabase(3, "join_test_catalog.txt")
233 | if err != nil {
234 | t.Fatalf(err.Error())
235 | }
236 |
237 | hf1, err := c.GetTable("test")
238 | if err != nil {
239 | t.Fatalf(err.Error())
240 | }
241 | hf2, err := c.GetTable("test2")
242 | if err != nil {
243 | t.Fatalf(err.Error())
244 | }
245 |
246 | var t1 = Tuple{
247 | Desc: *hf1.Descriptor(),
248 | Fields: []DBValue{
249 | StringField{"sam"},
250 | IntField{25},
251 | }}
252 |
253 | var t2 = Tuple{
254 | Desc: *hf2.Descriptor(),
255 | Fields: []DBValue{
256 | StringField{"george jones"},
257 | IntField{25},
258 | }}
259 |
260 | tid := NewTID()
261 | bp.BeginTransaction(tid)
262 |
263 | insertTupleForTest(t, hf1, &t1, tid)
264 | insertTupleForTest(t, hf2, &t2, tid)
265 |
266 | leftField := FieldExpr{t1.Desc.Fields[1]}
267 | rightField := FieldExpr{t2.Desc.Fields[1]}
268 |
269 | join, err := NewJoin(hf1, &leftField, hf2, &rightField, 100)
270 | if err != nil {
271 | t.Errorf("unexpected error initializing join")
272 | return
273 | }
274 | iter, err := join.Iterator(tid)
275 | if err != nil {
276 | t.Fatalf(err.Error())
277 | }
278 | if iter == nil {
279 | t.Fatalf("iter was nil")
280 | }
281 |
282 | var tdExpected = TupleDesc{Fields: []FieldType{
283 | {Fname: "a", Ftype: StringType},
284 | {Fname: "b", Ftype: IntType},
285 | {Fname: "c", Ftype: StringType},
286 | {Fname: "d", Ftype: IntType},
287 | }}
288 |
289 | tj, err := iter()
290 | if err != nil {
291 | t.Fatalf(err.Error())
292 | }
293 |
294 | if !tdExpected.equals(&tj.Desc) {
295 | t.Fatalf("Unexpected descriptor of joined tuple")
296 | }
297 | }
298 |
299 | func TestJoinTupleNil(t *testing.T) {
300 | _, t1, t2, _, _, _ := makeTestVars(t)
301 | tNew := joinTuples(&t1, nil)
302 | if !tNew.equals(&t1) {
303 | t.Fatalf("Unexpected output of joinTuple with nil")
304 | }
305 | tNew2 := joinTuples(nil, &t2)
306 | if !tNew2.equals(&t2) {
307 | t.Fatalf("Unexpected output of joinTuple with nil")
308 | }
309 | }
310 |
--------------------------------------------------------------------------------
/godb/join_optimizer.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | // Estimate the cost of a join j given the cardinalities (card1, card2) and
4 | // estimated costs (cost1, cost2) of the left and right sides of the join,
5 | // respectively.
6 | //
7 | // The cost of the join should be calculated based on the join algorithm (or
8 | // algorithms) that you implemented for Lab 2. It should be a function of the
9 | // amount of data that must be read over the course of the query, as well as the
10 | // number of CPU opertions performed by your join. Assume that the cost of a
11 | // single predicate application is roughly 1.
12 | func EstimateJoinCost(card1 int, card2 int, cost1 float64, cost2 float64) float64 {
13 | // Dummy implementation. Do not worry about this.
14 | return -1.0
15 | }
16 |
17 | // Estimate the cardinality of the result of a join between two tables, given
18 | // the join operator, primary key information, and table statistics.
19 | func EstimateJoinCardinality(t1card int, t2card int) int {
20 | // Dummy implementation. Do not worry about this.
21 | return -1
22 | }
23 |
24 | type TableInfo struct {
25 | name string // Name of the table
26 | stats Stats // Statistics for the table; may be nil if no stats are available
27 | sel float64 // Selectivity of the filters on the table
28 | }
29 |
30 | // A JoinNode represents a join between two tables.
31 | type JoinNode struct {
32 | leftTable TableInfo
33 | leftField string
34 |
35 | rightTable TableInfo
36 | rightField string
37 | }
38 |
39 | // Given a list of joins, table statistics, and selectivities, return the best
40 | // order in which to join the tables.
41 | //
42 | // selectivity is a map from table aliases to the selectivity of the filters on
43 | // that table. Note that LogicalJoinNodes contain LogicalSelectNodes that define
44 | // tables to join. Inside a LogicalSelectNode, there is both a table name
45 | // (table) and an alias. We may apply different filters to the same base table
46 | // but with different aliases, so the selectivity map contains selectivities for
47 | // a particular alias, not for a base table.
48 | func OrderJoins(joins []*JoinNode) ([]*JoinNode, error) {
49 | // Dummy implementation. Do not worry about this.
50 | return joins, nil
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/godb/join_test_catalog.txt:
--------------------------------------------------------------------------------
1 | test(a string, b int)
2 | test2(c string, d int)
3 |
--------------------------------------------------------------------------------
/godb/lab1_query.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | /*
8 | computeFieldSum should (1) load the csv file named fileName into a heap file
9 | (see [HeapFile.LoadFromCSV]), (2) compute the sum of the integer field named
10 | sumField string and, (3) return its value as an int.
11 |
12 | The supplied csv file is comma delimited and has a header.
13 |
14 | If the file doesn't exist, can't be opened, the field doesn't exist, or the
15 | field is not an integer, you should return an error.
16 |
17 | Note that when you create a HeapFile, you will need to supply a file name;
18 | you can supply a non-existant file, in which case it will be created.
19 | However, subsequent invocations of this method will result in tuples being
20 | reinserted into this file unless you delete (e.g., with [os.Remove] it before
21 | calling NewHeapFile.
22 |
23 | Note that you should NOT pass fileName into NewHeapFile -- fileName is a CSV
24 | file that you should call LoadFromCSV on.
25 | */
26 | func computeFieldSum(bp *BufferPool, fileName string, td TupleDesc, sumField string) (int, error) {
27 | return 0, fmt.Errorf("computeFieldSum not implemented") // replace me
28 | }
29 |
--------------------------------------------------------------------------------
/godb/lab1_query_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "testing"
7 | )
8 |
9 | func TestLab1Query(t *testing.T) {
10 | if os.Getenv("LAB") == "5" {
11 | t.Skip("This test is only valid up to Lab 4. Skipping")
12 | }
13 | bp, _, err := MakeTestDatabase(10, "catalog.txt")
14 | if err != nil {
15 | t.Fatalf("Failed to initialize test database")
16 | }
17 | f1 := FieldType{"name", "", StringType}
18 | f2 := FieldType{"age", "", IntType}
19 | td := TupleDesc{[]FieldType{f1, f2}}
20 | sum, err := computeFieldSum(bp, "lab1_test.csv", td, "age")
21 | if err != nil {
22 | fmt.Println(err)
23 | }
24 | if sum != 1111 {
25 | t.Fatalf("expected sum of 1111, got %d", sum)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/godb/lab1_test.csv:
--------------------------------------------------------------------------------
1 | name, age
2 | sam, 1
3 | tim, 10
4 | ferdi, 100
5 | ziniu, 1000
--------------------------------------------------------------------------------
/godb/limit_op.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | type LimitOp struct {
8 | // Required fields for parser
9 | child Operator
10 | limitTups Expr
11 | // Add additional fields here, if needed
12 | }
13 |
14 | // Construct a new limit operator. lim is how many tuples to return and child is
15 | // the child operator.
16 | func NewLimitOp(lim Expr, child Operator) *LimitOp {
17 | return &LimitOp{child, lim}
18 | }
19 |
20 | // Return a TupleDescriptor for this limit.
21 | func (l *LimitOp) Descriptor() *TupleDesc {
22 | // TODO: some code goes here
23 | return &TupleDesc{} // replace me
24 | }
25 |
26 | // Limit operator implementation. This function should iterate over the results
27 | // of the child iterator, and limit the result set to the first [lim] tuples it
28 | // sees (where lim is specified in the constructor).
29 | func (l *LimitOp) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
30 | // TODO: some code goes here
31 | return nil, fmt.Errorf("LimitOp.Iterator not implemented") // replace me
32 | }
33 |
--------------------------------------------------------------------------------
/godb/limit_op_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func testLimitCount(t *testing.T, n int) {
8 | t.Helper()
9 | _, t1, t2, hf, bp, _ := makeTestVars(t)
10 |
11 | for i := 0; i < n; i++ {
12 | tid := NewTID()
13 | bp.BeginTransaction(tid)
14 | err := hf.insertTuple(&t1, tid)
15 | if err != nil {
16 | t.Errorf(err.Error())
17 | return
18 | }
19 | err = hf.insertTuple(&t2, tid)
20 | if err != nil {
21 | t.Errorf(err.Error())
22 | return
23 | }
24 |
25 | // hack to force dirty pages to disk
26 | // because CommitTransaction may not be implemented
27 | // yet if this is called in lab 2
28 | if i%10 == 0 {
29 | bp.FlushAllPages()
30 | }
31 |
32 | //commit frequently to prevent buffer pool from filling
33 | bp.CommitTransaction(tid)
34 | }
35 |
36 | // check results
37 | tid := NewTID()
38 | bp.BeginTransaction(tid)
39 | lim := NewLimitOp(&ConstExpr{IntField{int64(n)}, IntType}, hf)
40 | if lim == nil {
41 | t.Fatalf("Op was nil")
42 | return
43 | }
44 | iter, err := lim.Iterator(tid)
45 | if err != nil {
46 | t.Fatalf(err.Error())
47 | return
48 | }
49 | if iter == nil {
50 | t.Fatalf("Iterator was nil")
51 | return
52 | }
53 |
54 | cnt := 0
55 | for {
56 | tup, _ := iter()
57 | if tup == nil {
58 | break
59 | }
60 | cnt++
61 | }
62 | if cnt != n {
63 | t.Errorf("unexpected number of results")
64 | }
65 |
66 | bp.CommitTransaction(tid)
67 | }
68 |
69 | func TestLimit5(t *testing.T) {
70 | testLimitCount(t, 5)
71 | }
72 |
73 | func TestLimit50(t *testing.T) {
74 | testLimitCount(t, 50)
75 | }
76 |
77 | func TestLimit100(t *testing.T) {
78 | testLimitCount(t, 100)
79 | }
80 |
--------------------------------------------------------------------------------
/godb/locking_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "sync"
5 | "testing"
6 | "time"
7 | )
8 |
9 | type LockGrabber struct {
10 | bp *BufferPool
11 | tid TransactionID
12 | file DBFile
13 | pgNo int
14 | perm RWPerm
15 |
16 | acq bool
17 | err error
18 | alock, elock sync.Mutex
19 | }
20 |
21 | func NewLockGrabber(bp *BufferPool, tid TransactionID, file DBFile, pgNo int, perm RWPerm) *LockGrabber {
22 | return &LockGrabber{bp, tid, file, pgNo, perm,
23 | false, nil, sync.Mutex{}, sync.Mutex{}}
24 | }
25 |
26 | func (lg *LockGrabber) run() {
27 | // Try to get the page from the buffer pool.
28 | _, err := lg.bp.GetPage(lg.file, lg.pgNo, lg.tid, lg.perm)
29 | if err == nil {
30 | lg.alock.Lock()
31 | lg.acq = true
32 | lg.alock.Unlock()
33 | } else {
34 | lg.elock.Lock()
35 | lg.err = err
36 | lg.elock.Unlock()
37 |
38 | lg.bp.AbortTransaction(lg.tid)
39 | }
40 | }
41 |
42 | func (lg *LockGrabber) acquired() bool {
43 | lg.alock.Lock()
44 | defer lg.alock.Unlock()
45 | return lg.acq
46 | }
47 |
48 | func (lg *LockGrabber) getError() error {
49 | lg.elock.Lock()
50 | defer lg.elock.Unlock()
51 | return lg.err
52 | }
53 |
54 | func startGrabber(bp *BufferPool, tid TransactionID, file DBFile, pgNo int, perm RWPerm) *LockGrabber {
55 | lg := NewLockGrabber(bp, tid, file, pgNo, perm)
56 | go lg.run()
57 | return lg
58 | }
59 |
60 | func grabLock(t *testing.T,
61 | bp *BufferPool, tid TransactionID, file DBFile, pgNo int, perm RWPerm,
62 | expected bool) {
63 |
64 | lg := startGrabber(bp, tid, file, pgNo, perm)
65 |
66 | time.Sleep(100 * time.Millisecond)
67 |
68 | var acquired bool = lg.acquired()
69 | if expected != acquired {
70 | t.Errorf("Expected %t, found %t", expected, acquired)
71 | }
72 |
73 | // TODO how to kill stalling lg?
74 | }
75 |
76 | func metaLockTester(t *testing.T, bp *BufferPool,
77 | tid1 TransactionID, file1 DBFile, pgNo1 int, perm1 RWPerm,
78 | tid2 TransactionID, file2 DBFile, pgNo2 int, perm2 RWPerm,
79 | expected bool) {
80 | bp.GetPage(file1, pgNo1, tid1, perm1)
81 | grabLock(t, bp, tid2, file2, pgNo2, perm2, expected)
82 | }
83 |
84 | func lockingTestSetUp(t *testing.T) (*BufferPool, *HeapFile, TransactionID, TransactionID) {
85 | bp, hf, tid1, tid2, _ := transactionTestSetUp(t)
86 | return bp, hf, tid1, tid2
87 | }
88 |
89 | func TestAcquireReadLocksOnSamePage(t *testing.T) {
90 | bp, hf, tid1, tid2 := lockingTestSetUp(t)
91 | metaLockTester(t, bp,
92 | tid1, hf, 0, ReadPerm,
93 | tid2, hf, 0, ReadPerm,
94 | true)
95 | }
96 |
97 | func TestAcquireReadWriteLocksOnSamePage(t *testing.T) {
98 | bp, hf, tid1, tid2 := lockingTestSetUp(t)
99 | metaLockTester(t, bp,
100 | tid1, hf, 0, ReadPerm,
101 | tid2, hf, 0, WritePerm,
102 | false)
103 | }
104 |
105 | func TestAcquireWriteReadLocksOnSamePage(t *testing.T) {
106 | bp, hf, tid1, tid2 := lockingTestSetUp(t)
107 | metaLockTester(t, bp,
108 | tid1, hf, 0, WritePerm,
109 | tid2, hf, 0, ReadPerm,
110 | false)
111 | }
112 |
113 | func TestAcquireReadWriteLocksOnTwoPages(t *testing.T) {
114 | bp, hf, tid1, tid2 := lockingTestSetUp(t)
115 | metaLockTester(t, bp,
116 | tid1, hf, 0, ReadPerm,
117 | tid2, hf, 1, WritePerm,
118 | true)
119 | }
120 |
121 | func TestAcquireWriteLocksOnTwoPages(t *testing.T) {
122 | bp, hf, tid1, tid2 := lockingTestSetUp(t)
123 | metaLockTester(t, bp,
124 | tid1, hf, 0, WritePerm,
125 | tid2, hf, 1, WritePerm,
126 | true)
127 | }
128 |
129 | func TestAcquireReadLocksOnTwoPages(t *testing.T) {
130 | bp, hf, tid1, tid2 := lockingTestSetUp(t)
131 | metaLockTester(t, bp,
132 | tid1, hf, 0, ReadPerm,
133 | tid2, hf, 1, ReadPerm,
134 | true)
135 | }
136 |
137 | func TestLockUpgrade(t *testing.T) {
138 | bp, hf, tid1, tid2 := lockingTestSetUp(t)
139 | metaLockTester(t, bp,
140 | tid1, hf, 0, ReadPerm,
141 | tid1, hf, 0, WritePerm,
142 | true)
143 | metaLockTester(t, bp,
144 | tid2, hf, 1, ReadPerm,
145 | tid2, hf, 1, WritePerm,
146 | true)
147 | }
148 |
149 | func TestAcquireWriteAndReadLocks(t *testing.T) {
150 | bp, hf, tid1, _ := lockingTestSetUp(t)
151 | metaLockTester(t, bp,
152 | tid1, hf, 0, WritePerm,
153 | tid1, hf, 0, ReadPerm,
154 | true)
155 | }
156 |
--------------------------------------------------------------------------------
/godb/mem_file.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | type MemPage struct {
4 | file *MemFile
5 | tuple Tuple
6 | }
7 |
8 | type MemFile struct {
9 | fileNo int
10 | desc *TupleDesc
11 | pages []*MemPage
12 | }
13 |
14 | func (mp *MemPage) isDirty() bool {
15 | return false
16 | }
17 |
18 | func (mp *MemPage) setDirty(tid TransactionID, dirty bool) {
19 | }
20 |
21 | func (mp *MemPage) getFile() DBFile {
22 | return mp.file
23 | }
24 |
25 | func (mf *MemFile) NumPages() int {
26 | return len(mf.pages)
27 | }
28 |
29 | func (mf *MemFile) insertTuple(t *Tuple, tid TransactionID) error {
30 | for i := range mf.pages {
31 | if mf.pages[i] == nil {
32 | t.Rid = i
33 | mf.pages[i] = &MemPage{file: mf, tuple: *t}
34 | }
35 | }
36 | t.Rid = len(mf.pages)
37 | mf.pages = append(mf.pages, &MemPage{file: mf, tuple: *t})
38 | return nil
39 | }
40 |
41 | func (mf *MemFile) deleteTuple(t *Tuple, tid TransactionID) error {
42 | mf.pages[t.Rid.(int)] = nil
43 | return nil
44 | }
45 |
46 | func (mf *MemFile) readPage(pageNo int) (Page, error) {
47 | return mf.pages[pageNo], nil
48 | }
49 |
50 | func (mf *MemFile) flushPage(page Page) error {
51 | return nil
52 | }
53 |
54 | type MemPageKey struct {
55 | fileNo int
56 | pgNo int
57 | }
58 |
59 | func (mf *MemFile) pageKey(pgNo int) any {
60 | return MemPageKey{mf.fileNo, pgNo}
61 | }
62 |
63 | func (mf *MemFile) Descriptor() *TupleDesc {
64 | return mf.desc
65 | }
66 |
67 | func (mf *MemFile) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
68 | i := 0
69 | return func() (*Tuple, error) {
70 | for {
71 | if i >= len(mf.pages) {
72 | return nil, nil
73 | }
74 | page := mf.pages[i]
75 | if page == nil {
76 | i++
77 | continue
78 | }
79 |
80 | i++
81 | return &page.tuple, nil
82 | }
83 | }, nil
84 | }
85 |
86 | func CreateMemFileFromTuples(tuples []Tuple) *MemFile {
87 | desc := tuples[0].Desc
88 | file := &MemFile{desc: &desc, pages: make([]*MemPage, len(tuples))}
89 | for _, t := range tuples {
90 | file.insertTuple(&t, 0)
91 | }
92 | return file
93 | }
94 |
--------------------------------------------------------------------------------
/godb/order_by_op.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | type OrderBy struct {
8 | orderBy []Expr // OrderBy should include these two fields (used by parser)
9 | child Operator
10 | // TODO: You may want to add additional fields here
11 | }
12 |
13 | // Construct an order by operator. Saves the list of field, child, and ascending
14 | // values for use in the Iterator() method. Here, orderByFields is a list of
15 | // expressions that can be extracted from the child operator's tuples, and the
16 | // ascending bitmap indicates whether the ith field in the orderByFields list
17 | // should be in ascending (true) or descending (false) order.
18 | func NewOrderBy(orderByFields []Expr, child Operator, ascending []bool) (*OrderBy, error) {
19 | // TODO: some code goes here
20 | return nil, fmt.Errorf("NewOrderBy not implemented.") //replace me
21 |
22 | }
23 |
24 | // Return the tuple descriptor.
25 | //
26 | // Note that the order by just changes the order of the child tuples, not the
27 | // fields that are emitted.
28 | func (o *OrderBy) Descriptor() *TupleDesc {
29 | // TODO: some code goes here
30 | return &TupleDesc{} // replace me
31 | }
32 |
33 | // Return a function that iterates through the results of the child iterator in
34 | // ascending/descending order, as specified in the constructor. This sort is
35 | // "blocking" -- it should first construct an in-memory sorted list of results
36 | // to return, and then iterate through them one by one on each subsequent
37 | // invocation of the iterator function.
38 | //
39 | // Although you are free to implement your own sorting logic, you may wish to
40 | // leverage the go sort package and the [sort.Sort] method for this purpose. To
41 | // use this you will need to implement three methods: Len, Swap, and Less that
42 | // the sort algorithm will invoke to produce a sorted list. See the first
43 | // example, example of SortMultiKeys, and documentation at:
44 | // https://pkg.go.dev/sort
45 | func (o *OrderBy) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
46 | // TODO: some code goes here
47 | return nil, fmt.Errorf("OrderBy.Iterator not implemented") // replace me
48 | }
49 |
--------------------------------------------------------------------------------
/godb/order_by_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "os"
5 | "testing"
6 | )
7 |
8 | func makeOrderByOrderingVars() (DBFile, Tuple, TupleDesc, *BufferPool, error) {
9 | var td = TupleDesc{Fields: []FieldType{
10 | {Fname: "a", Ftype: StringType},
11 | {Fname: "b", Ftype: IntType},
12 | {Fname: "c", Ftype: IntType},
13 | }}
14 |
15 | var t = Tuple{
16 | Desc: td,
17 | Fields: []DBValue{
18 | StringField{"sam"},
19 | IntField{25},
20 | IntField{5},
21 | }}
22 |
23 | bp, c, err := MakeTestDatabase(3, "catalog.txt")
24 | if err != nil {
25 | return nil, t, td, nil, err
26 | }
27 |
28 | os.Remove("test.dat")
29 | hf, err := c.addTable("test", td)
30 | if err != nil {
31 | return hf, t, td, nil, err
32 | }
33 |
34 | return hf, t, td, bp, nil
35 | }
36 |
37 | // test the order by operator, by asking it to sort the test database
38 | // in ascending and descending order and verifying the result
39 | func TestOrderBy(t *testing.T) {
40 | _, t1, t2, hf, _, tid := makeTestVars(t)
41 | hf.insertTuple(&t1, tid)
42 | hf.insertTuple(&t2, tid)
43 | bs := make([]bool, 2)
44 | for i := range bs {
45 | bs[i] = false
46 | }
47 | //order by name and then age, descending
48 | exprs := make([]Expr, len(t1.Desc.Fields))
49 | for i, f := range t1.Desc.Fields {
50 | exprs[i] = &FieldExpr{f}
51 | }
52 | oby, err := NewOrderBy(exprs, hf, bs)
53 | if err != nil {
54 | t.Fatalf(err.Error())
55 | }
56 |
57 | iter, _ := oby.Iterator(tid)
58 | if iter == nil {
59 | t.Fatalf("iter was nil")
60 | }
61 | var last string
62 | for {
63 | tup, _ := iter()
64 | if tup == nil {
65 | break
66 | }
67 | fval := tup.Fields[0].(StringField).Value
68 | if last != "" {
69 | if fval > last {
70 | t.Fatalf("data was not descending, as expected")
71 | }
72 | }
73 | last = fval
74 | }
75 |
76 | for i := range bs {
77 | bs[i] = true
78 | }
79 | //order by name and then age, ascending
80 | oby, err = NewOrderBy(exprs, hf, bs)
81 | if err != nil {
82 | t.Fatalf(err.Error())
83 | }
84 |
85 | iter, _ = oby.Iterator(tid)
86 | last = ""
87 | for {
88 | tup, _ := iter()
89 | if tup == nil {
90 | break
91 | }
92 | fval := tup.Fields[0].(StringField).Value
93 | if last != "" {
94 | if fval < last {
95 | t.Fatalf("data was not ascending, as expected")
96 | }
97 | }
98 | last = fval
99 | }
100 | }
101 |
102 | // harder order by test that inserts 4 tuples, and alternates ascending vs descending
103 | func TestOrderByMultiField(t *testing.T) {
104 | var td = TupleDesc{Fields: []FieldType{
105 | {Fname: "name", Ftype: StringType},
106 | {Fname: "age", Ftype: IntType},
107 | }}
108 |
109 | var t1 = Tuple{
110 | Desc: td,
111 | Fields: []DBValue{StringField{"sam"}, IntField{25}},
112 | Rid: nil,
113 | }
114 |
115 | var t2 = Tuple{
116 | Desc: td,
117 | Fields: []DBValue{StringField{"tim"}, IntField{44}},
118 | Rid: nil,
119 | }
120 |
121 | var t3 = Tuple{
122 | Desc: td,
123 | Fields: []DBValue{StringField{"mike"}, IntField{88}},
124 | Rid: nil,
125 | }
126 |
127 | var t4 = Tuple{
128 | Desc: td,
129 | Fields: []DBValue{StringField{"sam"}, IntField{26}},
130 | Rid: nil,
131 | }
132 |
133 | bp, c, err := MakeTestDatabase(2, "catalog.txt")
134 | if err != nil {
135 | t.Fatalf(err.Error())
136 | }
137 |
138 | os.Remove("test.dat")
139 | hf, err := c.addTable("test", td)
140 | if err != nil {
141 | t.Fatalf(err.Error())
142 | }
143 |
144 | tid := NewTID()
145 | bp.BeginTransaction(tid)
146 | hf.insertTuple(&t1, tid)
147 | hf.insertTuple(&t2, tid)
148 | hf.insertTuple(&t3, tid)
149 | hf.insertTuple(&t4, tid)
150 |
151 | //order by name and then age, descending
152 | ascDescs := [][]bool{{false, false}, {true, false}}
153 | expectedAnswers := [][]Tuple{{t2, t4, t1, t3}, {t3, t4, t1, t2}}
154 | exprs := make([]Expr, len(t1.Desc.Fields))
155 | for i, f := range t1.Desc.Fields {
156 | exprs[i] = &FieldExpr{f}
157 | }
158 |
159 | for i := 0; i < len(ascDescs); i++ {
160 | ascDesc := ascDescs[i]
161 | expected := expectedAnswers[i]
162 | result := []Tuple{}
163 | oby, err := NewOrderBy(exprs, hf, ascDesc)
164 | if err != nil {
165 | t.Fatalf(err.Error())
166 | }
167 | iter, _ := oby.Iterator(tid)
168 | if iter == nil {
169 | t.Fatalf("iter was nil")
170 | }
171 |
172 | for {
173 | tup, _ := iter()
174 | if tup == nil {
175 | break
176 | }
177 | result = append(result, *tup)
178 |
179 | }
180 | if len(result) != len(expected) {
181 | t.Fatalf("order by test %d produced different number of results than expected (%d got, expected %d)", i, len(result), len(expected))
182 | }
183 | for j, tup := range result {
184 | if !tup.equals(&expected[j]) {
185 | t.Fatalf("order by test %d got wrong tuple at position %d (expected %v, got %v)", i, j, expected[j].Fields, tup.Fields)
186 | }
187 | }
188 | }
189 |
190 | bp.CommitTransaction(tid)
191 | }
192 |
193 | func TestOrderByFieldsOrder(t *testing.T) {
194 | hf, tup, td, bp, err := makeOrderByOrderingVars()
195 | if err != nil {
196 | t.Fatalf(err.Error())
197 | }
198 |
199 | tid := NewTID()
200 | bp.BeginTransaction(tid)
201 | hf.insertTuple(&tup, tid)
202 |
203 | bs := make([]bool, 2)
204 | for i := range bs {
205 | bs[i] = false
206 | }
207 |
208 | exprs := []Expr{&FieldExpr{td.Fields[0]}, &FieldExpr{td.Fields[2]}}
209 |
210 | oby, err := NewOrderBy(exprs, hf, bs)
211 | if err != nil {
212 | t.Fatalf(err.Error())
213 | }
214 |
215 | iter, _ := oby.Iterator(tid)
216 | if iter == nil {
217 | t.Fatalf("iter was nil")
218 | }
219 |
220 | var expectedDesc = TupleDesc{Fields: []FieldType{
221 | {Fname: "a", Ftype: StringType},
222 | {Fname: "b", Ftype: IntType},
223 | {Fname: "c", Ftype: IntType},
224 | }}
225 |
226 | tupOut, err := iter()
227 | if err != nil {
228 | t.Fatalf(err.Error())
229 | }
230 |
231 | if !expectedDesc.equals(&tupOut.Desc) {
232 | t.Fatalf("Unexpected descriptor of ordered tuple")
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/godb/parser_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "os"
5 | )
6 |
7 | func MakeTestDatabase(bufferPoolSize int, catalog string) (*BufferPool, *Catalog, error) {
8 | bp, err := NewBufferPool(bufferPoolSize)
9 | if err != nil {
10 | return nil, nil, err
11 | }
12 |
13 | // load the catalog so we know which tables to remove
14 | c := NewCatalog(catalog, bp, "./")
15 | if err := c.parseCatalogFile(); err != nil {
16 | return nil, nil, err
17 | }
18 | for tableName := range c.tableMap {
19 | os.Remove(c.tableNameToFile(tableName))
20 | }
21 |
22 | // reload the catalog to reopen the table files
23 | c = NewCatalog(catalog, bp, "./")
24 | if err := c.parseCatalogFile(); err != nil {
25 | return nil, nil, err
26 | }
27 | return bp, c, nil
28 | }
29 |
30 | func MakeParserTestDatabase(bufferPoolSize int) (*BufferPool, *Catalog, error) {
31 | os.Remove("t2.dat")
32 | os.Remove("t.dat")
33 |
34 | bp, c, err := MakeTestDatabase(bufferPoolSize, "catalog.txt")
35 | if err != nil {
36 | return nil, nil, err
37 | }
38 |
39 | hf, err := c.GetTable("t")
40 | if err != nil {
41 | return nil, nil, err
42 | }
43 | hf2, err := c.GetTable("t2")
44 | if err != nil {
45 | return nil, nil, err
46 | }
47 |
48 | f, err := os.Open("testdb.txt")
49 | if err != nil {
50 | return nil, nil, err
51 | }
52 | err = hf.(*HeapFile).LoadFromCSV(f, true, ",", false)
53 | if err != nil {
54 | return nil, nil, err
55 | }
56 |
57 | f, err = os.Open("testdb.txt")
58 | if err != nil {
59 | return nil, nil, err
60 | }
61 | err = hf2.(*HeapFile).LoadFromCSV(f, true, ",", false)
62 | if err != nil {
63 | return nil, nil, err
64 | }
65 |
66 | if err := c.ComputeTableStats(); err != nil {
67 | return nil, nil, err
68 | }
69 |
70 | return bp, c, nil
71 | }
72 |
--------------------------------------------------------------------------------
/godb/project_op.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import "fmt"
4 |
5 | type Project struct {
6 | selectFields []Expr // required fields for parser
7 | outputNames []string
8 | child Operator
9 | // You may want to add additional fields here
10 | // TODO: some code goes here
11 | }
12 |
13 | // Construct a projection operator. It saves the list of selected field, child,
14 | // and the child op. Here, selectFields is a list of expressions that represents
15 | // the fields to be selected, outputNames are names by which the selected fields
16 | // are named (should be same length as selectFields; throws error if not),
17 | // distinct is for noting whether the projection reports only distinct results,
18 | // and child is the child operator.
19 | func NewProjectOp(selectFields []Expr, outputNames []string, distinct bool, child Operator) (Operator, error) {
20 | // TODO: some code goes here
21 | return nil, fmt.Errorf("NewProjectOp not implemented.") // replace me
22 | }
23 |
24 | // Return a TupleDescriptor for this projection. The returned descriptor should
25 | // contain fields for each field in the constructor selectFields list with
26 | // outputNames as specified in the constructor.
27 | //
28 | // HINT: you can use expr.GetExprType() to get the field type
29 | func (p *Project) Descriptor() *TupleDesc {
30 | // TODO: some code goes here
31 | return &TupleDesc{} // replace me
32 |
33 | }
34 |
35 | // Project operator implementation. This function should iterate over the
36 | // results of the child iterator, projecting out the fields from each tuple. In
37 | // the case of distinct projection, duplicate tuples should be removed. To
38 | // implement this you will need to record in some data structure with the
39 | // distinct tuples seen so far. Note that support for the distinct keyword is
40 | // optional as specified in the lab 2 assignment.
41 | func (p *Project) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
42 | // TODO: some code goes here
43 | return nil, fmt.Errorf("Project.Iterator not implemented") // replace me
44 | }
45 |
--------------------------------------------------------------------------------
/godb/project_op_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestProject(t *testing.T) {
8 | _, t1, t2, hf, _, tid := makeTestVars(t)
9 | hf.insertTuple(&t1, tid)
10 | hf.insertTuple(&t2, tid)
11 | //fs := make([]FieldType, 1)
12 | //fs[0] = t1.Desc.Fields[0]
13 | var outNames []string = make([]string, 1)
14 | outNames[0] = "outf"
15 | fieldExpr := FieldExpr{t1.Desc.Fields[0]}
16 | proj, _ := NewProjectOp([]Expr{&fieldExpr}, outNames, false, hf)
17 | if proj == nil {
18 | t.Fatalf("project was nil")
19 | }
20 | iter, _ := proj.Iterator(tid)
21 | if iter == nil {
22 | t.Fatalf("iter was nil")
23 | }
24 | tup, err := iter()
25 | if err != nil {
26 | t.Fatalf(err.Error())
27 | }
28 | if len(tup.Fields) != 1 || tup.Desc.Fields[0].Fname != "outf" {
29 | t.Errorf("invalid output tuple")
30 | }
31 |
32 | }
33 |
34 | func TestProjectDistinctOptional(t *testing.T) {
35 | _, t1, t2, hf, _, tid := makeTestVars(t)
36 | hf.insertTuple(&t1, tid)
37 | hf.insertTuple(&t2, tid)
38 | hf.insertTuple(&t1, tid)
39 | hf.insertTuple(&t2, tid)
40 |
41 | //fs := make([]FieldType, 1)
42 | //fs[0] = t1.Desc.Fields[0]
43 | var outNames []string = make([]string, 1)
44 | outNames[0] = "outf"
45 | fieldExpr := FieldExpr{t1.Desc.Fields[0]}
46 | proj, _ := NewProjectOp([]Expr{&fieldExpr}, outNames, true, hf)
47 | if proj == nil {
48 | t.Fatalf("project was nil")
49 | }
50 | iter, _ := proj.Iterator(tid)
51 | if iter == nil {
52 | t.Fatalf("iter was nil")
53 | }
54 | cnt := 0
55 | for {
56 | tup, err := iter()
57 | if err != nil {
58 | t.Fatalf(err.Error())
59 | }
60 | if tup == nil {
61 | break
62 | }
63 | cnt = cnt + 1
64 | }
65 | if cnt != 2 {
66 | t.Errorf("expected two names, got %d", cnt)
67 |
68 | }
69 | }
70 |
71 | func TestProjectOrdering(t *testing.T) {
72 | hf, tup, td, bp, err := makeOrderByOrderingVars()
73 | if err != nil {
74 | t.Fatalf(err.Error())
75 | }
76 |
77 | tid := NewTID()
78 | bp.BeginTransaction(tid)
79 | hf.insertTuple(&tup, tid)
80 |
81 | var outNames = []string{"out1", "out2"}
82 | exprs := []Expr{&FieldExpr{td.Fields[2]}, &FieldExpr{td.Fields[0]}}
83 |
84 | proj, _ := NewProjectOp(exprs, outNames, false, hf)
85 | if proj == nil {
86 | t.Fatalf("project was nil")
87 | }
88 | iter, _ := proj.Iterator(tid)
89 | if iter == nil {
90 | t.Fatalf("iter was nil")
91 | }
92 |
93 | tupOut, err := iter()
94 | if err != nil {
95 | t.Fatalf(err.Error())
96 | }
97 |
98 | var expectedDesc = TupleDesc{Fields: []FieldType{
99 | {Fname: "out1", Ftype: IntType},
100 | {Fname: "out2", Ftype: StringType},
101 | }}
102 |
103 | if !expectedDesc.equals(&tupOut.Desc) {
104 | t.Fatalf("Unexpected descriptor of projected tuple")
105 | }
106 |
107 | }
108 |
109 | func TestProjectExtra(t *testing.T) {
110 | _, _, t1, _, _ := makeJoinOrderingVars(t)
111 | ft1 := FieldType{"a", "", StringType}
112 | ft2 := FieldType{"b", "", IntType}
113 | outTup, _ := t1.project([]FieldType{ft1})
114 | if (len(outTup.Fields)) != 1 {
115 | t.Fatalf("project returned %d fields, expected 1", len(outTup.Fields))
116 | }
117 | v, ok := outTup.Fields[0].(StringField)
118 |
119 | if !ok {
120 | t.Fatalf("project of name didn't return string")
121 | }
122 | if v.Value != "sam" {
123 | t.Fatalf("project didn't return sam")
124 |
125 | }
126 | outTup, _ = t1.project([]FieldType{ft2})
127 | if (len(outTup.Fields)) != 1 {
128 | t.Fatalf("project returned %d fields, expected 1", len(outTup.Fields))
129 | }
130 | v2, ok := outTup.Fields[0].(IntField)
131 |
132 | if !ok {
133 | t.Fatalf("project of name didn't return int")
134 | }
135 | if v2.Value != 25 {
136 | t.Fatalf("project didn't return 25")
137 |
138 | }
139 |
140 | outTup, _ = t1.project([]FieldType{ft2, ft1})
141 | if (len(outTup.Fields)) != 2 {
142 | t.Fatalf("project returned %d fields, expected 2", len(outTup.Fields))
143 | }
144 | v, ok = outTup.Fields[1].(StringField)
145 | if !ok {
146 | t.Fatalf("project of name didn't return string in second field")
147 | }
148 | if v.Value != "sam" {
149 | t.Fatalf("project didn't return sam")
150 |
151 | }
152 |
153 | v2, ok = outTup.Fields[0].(IntField)
154 | if !ok {
155 | t.Fatalf("project of name didn't return int in first field")
156 | }
157 | if v2.Value != 25 {
158 | t.Fatalf("project didn't return 25")
159 |
160 | }
161 |
162 | }
163 |
164 | func TestTupleProjectExtra(t *testing.T) {
165 | var td = TupleDesc{Fields: []FieldType{
166 | {Fname: "name1", TableQualifier: "tq1", Ftype: StringType},
167 | {Fname: "name2", TableQualifier: "tq2", Ftype: StringType},
168 | {Fname: "name1", TableQualifier: "tq2", Ftype: StringType},
169 | }}
170 |
171 | var t1 = Tuple{
172 | Desc: td,
173 | Fields: []DBValue{
174 | StringField{"SFname1tq1"},
175 | StringField{"SFname2tq2"},
176 | StringField{"SFname1tq2"},
177 | }}
178 |
179 | t2, err := t1.project([]FieldType{
180 | {Fname: "name1", TableQualifier: "tq1", Ftype: StringType},
181 | {Fname: "name2", TableQualifier: "", Ftype: StringType},
182 | {Fname: "name1", TableQualifier: "tq1", Ftype: StringType},
183 | {Fname: "name2", TableQualifier: "tq2", Ftype: StringType},
184 | {Fname: "name1", TableQualifier: "tq2", Ftype: StringType},
185 | })
186 |
187 | if err != nil {
188 | t.Fatalf("%v", err)
189 | }
190 |
191 | if t2.Fields[0].(StringField).Value != "SFname1tq1" {
192 | t.Fatalf("tuple project extra wrong match")
193 | }
194 |
195 | if t2.Fields[1].(StringField).Value != "SFname2tq2" {
196 | t.Fatalf("tuple project extra wrong match")
197 | }
198 |
199 | if t2.Fields[2].(StringField).Value != "SFname1tq1" {
200 | t.Fatalf("tuple project extra wrong match")
201 | }
202 | if t2.Fields[3].(StringField).Value != "SFname2tq2" {
203 | t.Fatalf("tuple project extra wrong match")
204 | }
205 | if t2.Fields[4].(StringField).Value != "SFname1tq2" {
206 | t.Fatalf("tuple project extra wrong match")
207 | }
208 |
209 | }
210 |
--------------------------------------------------------------------------------
/godb/savedresults/q1-easy-result.csv:
--------------------------------------------------------------------------------
1 | s
2 | 124
3 | 45
4 | 30
5 | 22
6 | 40
7 | 50
8 | 60
9 | 65
10 | 99
11 | 38
12 |
--------------------------------------------------------------------------------
/godb/savedresults/q10-easy-result.csv:
--------------------------------------------------------------------------------
1 | name,age
2 | sam,25
3 | kathy,45
4 | bill,30
5 | ang,22
6 | joe,40
7 | riza,43
8 | riza,22
9 | pat,38
10 | sam,25
11 | riza,43
12 | riza,22
13 |
--------------------------------------------------------------------------------
/godb/savedresults/q11-easy-result.csv:
--------------------------------------------------------------------------------
1 | name
2 | ang
3 | bill
4 | bo
5 | joe
6 | kathy
7 | mark
8 | pat
9 | riza
10 | riza
11 | sam
12 | sam
13 | sarah
14 |
--------------------------------------------------------------------------------
/godb/savedresults/q12-easy-result.csv:
--------------------------------------------------------------------------------
1 | age,count(*)
2 | 25,1
3 | 45,1
4 | 30,1
5 | 22,2
6 | 40,1
7 | 50,1
8 | 60,1
9 | 43,1
10 | 99,2
11 | 38,1
12 |
--------------------------------------------------------------------------------
/godb/savedresults/q2-easy-result.csv:
--------------------------------------------------------------------------------
1 | sum(age),sum(age)
2 | 693,573
3 |
--------------------------------------------------------------------------------
/godb/savedresults/q3-easy-result.csv:
--------------------------------------------------------------------------------
1 | +
2 | 121
3 |
--------------------------------------------------------------------------------
/godb/savedresults/q4-easy-result.csv:
--------------------------------------------------------------------------------
1 | t.name,t.age
2 | ang,22
3 | riza,22
4 | sam,25
5 |
--------------------------------------------------------------------------------
/godb/savedresults/q5-easy-result.csv:
--------------------------------------------------------------------------------
1 | name,age
2 | ang,22
3 | ang,22
4 | riza,22
5 | riza,22
6 | riza,22
7 | riza,22
8 | sam,25
9 | sam,25
10 | bill,30
11 | pat,38
12 | joe,40
13 | riza,43
14 | riza,43
15 | kathy,45
16 |
--------------------------------------------------------------------------------
/godb/savedresults/q6-easy-result.csv:
--------------------------------------------------------------------------------
1 | sq
2 | 625
3 | 625
4 | 625
5 | 625
6 | 625
7 | 625
8 | 625
9 | 625
10 | 625
11 | 625
12 | 625
13 | 625
14 |
--------------------------------------------------------------------------------
/godb/savedresults/q7-easy-result.csv:
--------------------------------------------------------------------------------
1 | 1,name
2 | 1,sam
3 | 1,kathy
4 | 1,bill
5 | 1,ang
6 | 1,joe
7 | 1,mark
8 | 1,sarah
9 | 1,riza
10 | 1,bo
11 | 1,pat
12 | 1,sam
13 | 1,riza
14 |
--------------------------------------------------------------------------------
/godb/savedresults/q8-easy-result.csv:
--------------------------------------------------------------------------------
1 | age,name
2 | 25,sam
3 | 45,kathy
4 | 30,bill
5 | 22,ang
6 | 40,joe
7 | 50,mark
8 | 60,sarah
9 | 43,riza
10 | 99,bo
11 | 38,pat
12 | 99,sam
13 | 22,riza
14 |
--------------------------------------------------------------------------------
/godb/savedresults/q9-easy-result.csv:
--------------------------------------------------------------------------------
1 | name,totage
2 | sam,124
3 | kathy,45
4 | bill,30
5 | ang,22
6 | joe,40
7 | mark,50
8 | sarah,60
9 | riza,65
10 | bo,99
11 | pat,38
12 |
--------------------------------------------------------------------------------
/godb/simple_query_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "os"
5 | "testing"
6 | )
7 |
8 | func writeFile(t *testing.T, filename string, contents string) {
9 | f, err := os.Create(filename)
10 | if err != nil {
11 | t.Fatal(err)
12 | }
13 | defer f.Close()
14 | _, err = f.WriteString(contents)
15 | if err != nil {
16 | t.Fatal(err)
17 | }
18 | }
19 |
20 | func TestSimpleQuery(t *testing.T) {
21 | bp, c, err := MakeParserTestDatabase(10000)
22 | if err != nil {
23 | t.Fatalf("failed to create test database, %s", err.Error())
24 | }
25 |
26 | hf1, err := c.GetTable("t")
27 | if err != nil {
28 | t.Fatalf("no table t, %s", err.Error())
29 | }
30 | hf2, err := c.GetTable("t2")
31 | if err != nil {
32 | t.Fatalf("no table t2, %s", err.Error())
33 | }
34 |
35 | f_name := FieldExpr{FieldType{"name", "", StringType}}
36 | joinOp, err := NewJoin(hf1, &f_name, hf2, &f_name, 1000)
37 | if err != nil {
38 | t.Fatalf("failed to construct join, %s", err.Error())
39 | }
40 | f_age := FieldExpr{FieldType{"age", "t", IntType}}
41 | e_const := ConstExpr{IntField{30}, IntType}
42 | filterOp, err := NewFilter(&e_const, OpGt, &f_age, joinOp)
43 | if err != nil {
44 | t.Fatalf("failed to construct filter, %s", err.Error())
45 | }
46 | if filterOp == nil {
47 | t.Fatalf("filter op was nil")
48 | }
49 | if filterOp.Descriptor() == nil {
50 | t.Fatalf("filter op descriptor was nil")
51 | }
52 | sa := CountAggState{}
53 | if len(filterOp.Descriptor().Fields) == 0 {
54 | t.Fatalf("filter op descriptor has no fields")
55 | }
56 | expr := FieldExpr{filterOp.Descriptor().Fields[0]}
57 | sa.Init("count", &expr)
58 | agg := NewAggregator([]AggState{&sa}, filterOp)
59 | tid := NewTID()
60 | bp.BeginTransaction(tid)
61 | f, err := agg.Iterator(tid)
62 | if err != nil {
63 | t.Fatalf("failed to get iterator, %s", err.Error())
64 | }
65 | tup, err := f()
66 | if err != nil {
67 | t.Fatalf("failed to get tuple, %s", err.Error())
68 | }
69 | cnt2 := tup.Fields[0].(IntField).Value
70 | if cnt2 != 10 {
71 | t.Fatalf("expected 10 results, got %d", cnt2)
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/godb/table_stats_lab1.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | /*
4 | TableStats represents statistics (e.g., histograms) about base tables in a
5 | query.
6 | */
7 |
8 | // Interface for statistics that are maintained for a table.
9 | type Stats interface {
10 | EstimateScanCost() float64
11 | EstimateCardinality(selectivity float64) int
12 | EstimateSelectivity(field string, op BoolOp, value DBValue) (float64, error)
13 | }
14 |
15 | type TableStats struct {
16 | basePages int
17 | baseTups int
18 | histograms map[string]any
19 | tupleDesc *TupleDesc
20 | }
21 |
22 | // The default cost to read a page from disk. This value can be adjusted to
23 | // accommodate different storage devices.
24 | const CostPerPage = 1000
25 |
26 | // Number of bins for histograms. Feel free to increase this value over 100,
27 | // though our tests assume that you have at least 100 bins in your histograms.
28 | const NumHistBins = 100
29 |
30 | // Dummy Implementations to implement the interface
31 | func (ts *TableStats) EstimateScanCost() float64 {
32 | return 0
33 | }
34 | func (ts *TableStats) EstimateCardinality(selectivity float64) int {
35 | return 0
36 | }
37 | func (ts *TableStats) EstimateSelectivity(field string, op BoolOp, value DBValue) (float64, error) {
38 | return 0.5, nil
39 | }
--------------------------------------------------------------------------------
/godb/test_heap_file.csv:
--------------------------------------------------------------------------------
1 | name, age
2 | sam, 10
3 | joe, 20
4 | bill, 30
5 | sam, 10
6 | joe, 20
7 | bill, 30
8 | sam, 10
9 | joe, 20
10 | bill, 30
11 | sam, 10
12 | joe, 20
13 | bill, 30
14 | sam, 10
15 | joe, 20
16 | bill, 30
17 | sam, 10
18 | joe, 20
19 | bill, 30
20 | sam, 10
21 | joe, 20
22 | bill, 30
23 | sam, 10
24 | joe, 20
25 | bill, 30
26 | sam, 10
27 | joe, 20
28 | bill, 30
29 | sam, 10
30 | joe, 20
31 | bill, 30
32 | sam, 10
33 | joe, 20
34 | bill, 30
35 | sam, 10
36 | joe, 20
37 | bill, 30
38 | sam, 10
39 | joe, 20
40 | bill, 30
41 | sam, 10
42 | joe, 20
43 | bill, 30
44 | sam, 10
45 | joe, 20
46 | bill, 30
47 | sam, 10
48 | joe, 20
49 | bill, 30
50 | sam, 10
51 | joe, 20
52 | bill, 30
53 | sam, 10
54 | joe, 20
55 | bill, 30
56 | sam, 10
57 | joe, 20
58 | bill, 30
59 | sam, 10
60 | joe, 20
61 | bill, 30
62 | sam, 10
63 | joe, 20
64 | bill, 30
65 | sam, 10
66 | joe, 20
67 | bill, 30
68 | sam, 10
69 | joe, 20
70 | bill, 30
71 | sam, 10
72 | joe, 20
73 | bill, 30
74 | sam, 10
75 | joe, 20
76 | bill, 30
77 | sam, 10
78 | joe, 20
79 | bill, 30
80 | sam, 10
81 | joe, 20
82 | bill, 30
83 | sam, 10
84 | joe, 20
85 | bill, 30
86 | sam, 10
87 | joe, 20
88 | bill, 30
89 | sam, 10
90 | joe, 20
91 | bill, 30
92 | sam, 10
93 | joe, 20
94 | bill, 30
95 | sam, 10
96 | joe, 20
97 | bill, 30
98 | sam, 10
99 | joe, 20
100 | bill, 30
101 | sam, 10
102 | joe, 20
103 | bill, 30
104 | sam, 10
105 | joe, 20
106 | bill, 30
107 | sam, 10
108 | joe, 20
109 | bill, 30
110 | sam, 10
111 | joe, 20
112 | bill, 30
113 | sam, 10
114 | joe, 20
115 | bill, 30
116 | sam, 10
117 | joe, 20
118 | bill, 30
119 | sam, 10
120 | joe, 20
121 | bill, 30
122 | sam, 10
123 | joe, 20
124 | bill, 30
125 | sam, 10
126 | joe, 20
127 | bill, 30
128 | sam, 10
129 | joe, 20
130 | bill, 30
131 | sam, 10
132 | joe, 20
133 | bill, 30
134 | sam, 10
135 | joe, 20
136 | bill, 30
137 | sam, 10
138 | joe, 20
139 | bill, 30
140 | sam, 10
141 | joe, 20
142 | bill, 30
143 | sam, 10
144 | joe, 20
145 | bill, 30
146 | sam, 10
147 | joe, 20
148 | bill, 30
149 | sam, 10
150 | joe, 20
151 | bill, 30
152 | sam, 10
153 | joe, 20
154 | bill, 30
155 | sam, 10
156 | joe, 20
157 | bill, 30
158 | sam, 10
159 | joe, 20
160 | bill, 30
161 | sam, 10
162 | joe, 20
163 | bill, 30
164 | sam, 10
165 | joe, 20
166 | bill, 30
167 | sam, 10
168 | joe, 20
169 | bill, 30
170 | sam, 10
171 | joe, 20
172 | bill, 30
173 | sam, 10
174 | joe, 20
175 | bill, 30
176 | sam, 10
177 | joe, 20
178 | bill, 30
179 | sam, 10
180 | joe, 20
181 | bill, 30
182 | sam, 10
183 | joe, 20
184 | bill, 30
185 | sam, 10
186 | joe, 20
187 | bill, 30
188 | sam, 10
189 | joe, 20
190 | bill, 30
191 | sam, 10
192 | joe, 20
193 | bill, 30
194 | sam, 10
195 | joe, 20
196 | bill, 30
197 | sam, 10
198 | joe, 20
199 | bill, 30
200 | sam, 10
201 | joe, 20
202 | bill, 30
203 | sam, 10
204 | joe, 20
205 | bill, 30
206 | sam, 10
207 | joe, 20
208 | bill, 30
209 | sam, 10
210 | joe, 20
211 | bill, 30
212 | sam, 10
213 | joe, 20
214 | bill, 30
215 | sam, 10
216 | joe, 20
217 | bill, 30
218 | sam, 10
219 | joe, 20
220 | bill, 30
221 | sam, 10
222 | joe, 20
223 | bill, 30
224 | sam, 10
225 | joe, 20
226 | bill, 30
227 | sam, 10
228 | joe, 20
229 | bill, 30
230 | sam, 10
231 | joe, 20
232 | bill, 30
233 | sam, 10
234 | joe, 20
235 | bill, 30
236 | sam, 10
237 | joe, 20
238 | bill, 30
239 | sam, 10
240 | joe, 20
241 | bill, 30
242 | sam, 10
243 | joe, 20
244 | bill, 30
245 | sam, 10
246 | joe, 20
247 | bill, 30
248 | sam, 10
249 | joe, 20
250 | bill, 30
251 | sam, 10
252 | joe, 20
253 | bill, 30
254 | sam, 10
255 | joe, 20
256 | bill, 30
257 | sam, 10
258 | joe, 20
259 | bill, 30
260 | sam, 10
261 | joe, 20
262 | bill, 30
263 | sam, 10
264 | joe, 20
265 | bill, 30
266 | sam, 10
267 | joe, 20
268 | bill, 30
269 | sam, 10
270 | joe, 20
271 | bill, 30
272 | sam, 10
273 | joe, 20
274 | bill, 30
275 | sam, 10
276 | joe, 20
277 | bill, 30
278 | sam, 10
279 | joe, 20
280 | bill, 30
281 | sam, 10
282 | joe, 20
283 | bill, 30
284 | sam, 10
285 | joe, 20
286 | bill, 30
287 | sam, 10
288 | joe, 20
289 | bill, 30
290 | sam, 10
291 | joe, 20
292 | bill, 30
293 | sam, 10
294 | joe, 20
295 | bill, 30
296 | sam, 10
297 | joe, 20
298 | bill, 30
299 | sam, 10
300 | joe, 20
301 | bill, 30
302 | sam, 10
303 | joe, 20
304 | bill, 30
305 | sam, 10
306 | joe, 20
307 | bill, 30
308 | sam, 10
309 | joe, 20
310 | bill, 30
311 | sam, 10
312 | joe, 20
313 | bill, 30
314 | sam, 10
315 | joe, 20
316 | bill, 30
317 | sam, 10
318 | joe, 20
319 | bill, 30
320 | sam, 10
321 | joe, 20
322 | bill, 30
323 | sam, 10
324 | joe, 20
325 | bill, 30
326 | sam, 10
327 | joe, 20
328 | bill, 30
329 | sam, 10
330 | joe, 20
331 | bill, 30
332 | sam, 10
333 | joe, 20
334 | bill, 30
335 | sam, 10
336 | joe, 20
337 | bill, 30
338 | sam, 10
339 | joe, 20
340 | bill, 30
341 | sam, 10
342 | joe, 20
343 | bill, 30
344 | sam, 10
345 | joe, 20
346 | bill, 30
347 | sam, 10
348 | joe, 20
349 | bill, 30
350 | sam, 10
351 | joe, 20
352 | bill, 30
353 | sam, 10
354 | joe, 20
355 | bill, 30
356 | sam, 10
357 | joe, 20
358 | bill, 30
359 | sam, 10
360 | joe, 20
361 | bill, 30
362 | sam, 10
363 | joe, 20
364 | bill, 30
365 | sam, 10
366 | joe, 20
367 | bill, 30
368 | sam, 10
369 | joe, 20
370 | bill, 30
371 | sam, 10
372 | joe, 20
373 | bill, 30
374 | sam, 10
375 | joe, 20
376 | bill, 30
377 | sam, 10
378 | joe, 20
379 | bill, 30
380 | sam, 10
381 | joe, 20
382 | bill, 30
383 | sam, 10
384 | joe, 20
385 | bill, 30
--------------------------------------------------------------------------------
/godb/testdb.txt:
--------------------------------------------------------------------------------
1 | name,age
2 | sam,25
3 | kathy,45
4 | bill,30
5 | ang,22
6 | joe,40
7 | mark,50
8 | sarah,60
9 | riza,43
10 | bo,99
11 | pat,38
12 | sam,99
13 | riza,22
--------------------------------------------------------------------------------
/godb/transaction.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import "sync"
4 |
5 | type TransactionID int
6 |
7 | var nextTid = 0
8 | var newTidMutex sync.Mutex
9 |
10 | func NewTID() TransactionID {
11 | newTidMutex.Lock()
12 | defer newTidMutex.Unlock()
13 | id := nextTid
14 | nextTid++
15 | return TransactionID(id)
16 | }
17 |
18 | //var tid TransactionID = NewTID()
19 |
--------------------------------------------------------------------------------
/godb/transaction_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 | "os"
7 | "sync"
8 | "testing"
9 | "time"
10 | )
11 |
12 | // This test does not have credit and serves merely as a sanity check since
13 | // NewTID() should have been implemented for you.
14 | func TestTransactionTid(t *testing.T) {
15 | tid := NewTID()
16 | tid2 := NewTID()
17 | var tid3 = tid
18 | if tid == tid2 {
19 | t.Errorf("different transactions have same id")
20 | }
21 | if tid != tid3 {
22 | t.Errorf("same transactions have different id")
23 | }
24 | }
25 |
26 | const numConcurrentThreads int = 20
27 |
28 | var c chan int = make(chan int, numConcurrentThreads*2)
29 |
30 | func readXaction(hf DBFile, bp *BufferPool, wg *sync.WaitGroup) {
31 | for {
32 | start:
33 | tid := NewTID()
34 | bp.BeginTransaction(tid)
35 | pgCnt1 := hf.NumPages()
36 | it, _ := hf.Iterator(tid)
37 | cnt1 := 0
38 |
39 | for {
40 | t, err := it()
41 | if err != nil {
42 | // Assume this is because of a deadlock, restart txn
43 | time.Sleep(time.Duration(rand.Intn(8)) * 100 * time.Microsecond)
44 | goto start
45 | }
46 | if t == nil {
47 | break
48 | }
49 | cnt1++
50 | }
51 |
52 | it, _ = hf.Iterator(tid)
53 | cnt2 := 0
54 | for {
55 | t, err := it()
56 | if err != nil {
57 | // Assume this is because of a deadlock, restart txn
58 | time.Sleep(time.Duration(rand.Intn(8)) * 100 * time.Microsecond)
59 | goto start
60 | }
61 | if t == nil {
62 | break
63 | }
64 | cnt2++
65 | }
66 | if cnt1 == cnt2 || pgCnt1 != hf.NumPages() {
67 | //fmt.Printf("read same number of tuples both iterators (%d)\n", cnt1)
68 | c <- 1
69 | } else {
70 | fmt.Printf("ERROR: read different number of tuples both iterators (%d, %d)\n", cnt1, cnt2)
71 | c <- 0
72 | }
73 | bp.CommitTransaction(tid)
74 | wg.Done()
75 | return
76 | }
77 | }
78 |
79 | func writeXaction(hf DBFile, bp *BufferPool, writeTuple Tuple, wg *sync.WaitGroup) {
80 | for {
81 | start:
82 | tid := NewTID()
83 | bp.BeginTransaction(tid)
84 | for i := 0; i < 10; i++ {
85 | err := hf.insertTuple(&writeTuple, tid)
86 | if err != nil {
87 | // Assume this is because of a deadlock, restart txn
88 | time.Sleep(time.Duration(rand.Intn(8)) * 100 * time.Microsecond)
89 | goto start
90 | }
91 | }
92 | bp.CommitTransaction(tid)
93 | break
94 | }
95 | c <- 1
96 | wg.Done()
97 | }
98 |
99 | func TestTransactions(t *testing.T) {
100 | _, t1, t2, _, _, _ := makeTestVars(t)
101 | bp, catalog, err := MakeTestDatabase(20, "catalog.txt")
102 | if err != nil {
103 | t.Fatalf(err.Error())
104 | }
105 |
106 | tid := NewTID()
107 | bp.BeginTransaction(tid)
108 | hf, err := catalog.GetTable("t")
109 | if err != nil {
110 | t.Fatalf(err.Error())
111 | }
112 | var wg sync.WaitGroup
113 |
114 | for i := 0; i < 1000; i++ {
115 | err := hf.insertTuple(&t1, tid)
116 | if err != nil {
117 | fmt.Print(err.Error())
118 | t.Errorf("transaction test failed")
119 | }
120 | err = hf.insertTuple(&t2, tid)
121 | if err != nil {
122 | fmt.Print(err.Error())
123 | t.Errorf("transaction test failed")
124 | }
125 | }
126 | bp.CommitTransaction(tid)
127 |
128 | wg.Add(numConcurrentThreads * 2)
129 |
130 | for i := 0; i < numConcurrentThreads; i++ {
131 | go readXaction(hf, bp, &wg)
132 | //time.Sleep(2 * time.Millisecond)
133 | go writeXaction(hf, bp, t1, &wg)
134 | time.Sleep(10 * time.Millisecond)
135 | }
136 |
137 | wg.Wait()
138 |
139 | for i := 0; i < numConcurrentThreads*2; i++ {
140 | val := <-c
141 | if val == 0 {
142 | t.Errorf("transaction test failed")
143 | }
144 | }
145 |
146 | wg.Add(1)
147 | go readXaction(hf, bp, &wg)
148 | wg.Wait()
149 | }
150 |
151 | func transactionTestSetUpVarLen(t *testing.T, tupCnt int, pgCnt int) (*BufferPool, *HeapFile, TransactionID, TransactionID, Tuple, Tuple) {
152 | _, t1, t2, hf, bp, _ := makeTestVars(t)
153 |
154 | csvFile, err := os.Open(fmt.Sprintf("txn_test_%d_%d.csv", tupCnt, pgCnt))
155 | if err != nil {
156 | t.Fatalf("error opening test file")
157 | }
158 | hf.LoadFromCSV(csvFile, false, ",", false)
159 | if hf.NumPages() != pgCnt {
160 | t.Fatalf("error making test vars; unexpected number of pages")
161 | }
162 |
163 | tid1 := NewTID()
164 | bp.BeginTransaction(tid1)
165 | tid2 := NewTID()
166 | bp.BeginTransaction(tid2)
167 | return bp, hf, tid1, tid2, t1, t2
168 | }
169 |
170 | func transactionTestSetUp(t *testing.T) (*BufferPool, *HeapFile, TransactionID, TransactionID, Tuple) {
171 | bp, hf, tid1, tid2, t1, _ := transactionTestSetUpVarLen(t, 300, 3)
172 | return bp, hf, tid1, tid2, t1
173 | }
174 |
175 | func TestTransactionTwice(t *testing.T) {
176 | bp, hf, tid1, tid2, _ := transactionTestSetUp(t)
177 | bp.GetPage(hf, 0, tid1, ReadPerm)
178 | bp.GetPage(hf, 1, tid1, WritePerm)
179 | bp.CommitTransaction(tid1)
180 |
181 | bp.GetPage(hf, 0, tid2, WritePerm)
182 | bp.GetPage(hf, 1, tid2, WritePerm)
183 | }
184 |
185 | func testTransactionComplete(t *testing.T, commit bool) {
186 | bp, hf, tid1, tid2, t1 := transactionTestSetUp(t)
187 |
188 | pg, _ := bp.GetPage(hf, 2, tid1, WritePerm)
189 | heapp := pg.(*heapPage)
190 | heapp.insertTuple(&t1)
191 | heapp.setDirty(tid1, true)
192 |
193 | if commit {
194 | bp.CommitTransaction(tid1)
195 | } else {
196 | bp.AbortTransaction(tid1)
197 | }
198 |
199 | bp.FlushAllPages()
200 |
201 | pg, _ = bp.GetPage(hf, 2, tid2, WritePerm)
202 | heapp = pg.(*heapPage)
203 | iter := heapp.tupleIter()
204 |
205 | found := false
206 | for tup, err := iter(); tup != nil || err != nil; tup, err = iter() {
207 | if err != nil {
208 | t.Fatalf("Iterator error")
209 | }
210 | if t1.equals(tup) {
211 | found = true
212 | break
213 | }
214 | }
215 |
216 | if found != commit {
217 | t.Errorf("Expected %t, found %t", commit, found)
218 | }
219 | }
220 |
221 | func TestTransactionCommit(t *testing.T) {
222 | testTransactionComplete(t, true)
223 | }
224 |
225 | func TestTransactionAbort(t *testing.T) {
226 | testTransactionComplete(t, false)
227 | }
228 |
229 | // placeholder op for a singleton tuple
230 | type Singleton struct {
231 | tup Tuple
232 | ran bool
233 | }
234 |
235 | func (i *Singleton) Descriptor() *TupleDesc {
236 | return &i.tup.Desc
237 | }
238 |
239 | func (i *Singleton) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
240 | return func() (*Tuple, error) {
241 | if i.ran {
242 | return nil, nil
243 | }
244 | i.ran = true
245 | return &i.tup, nil
246 | }, nil
247 | }
248 |
249 | // Run threads transactions, each each of which reads a single tuple from a
250 | // page, deletes the tuple, and re-inserts it with an incremented value. There
251 | // will be deadlocks, so your deadlock handling will have to be correct to allow
252 | // all transactions to be committed and the value to be incremented threads
253 | // times.
254 | func validateTransactions(t *testing.T, threads int) {
255 | bp, hf, _, _, _, t2 := transactionTestSetUpVarLen(t, 1, 1)
256 |
257 | var startWg, readyWg sync.WaitGroup
258 | startChan := make(chan struct{})
259 |
260 | // sleep for an increasingly long time after deadlocks. this backoff helps avoid starvation
261 | nDeadlocks := 0
262 | var nDeadlocksMutex sync.Mutex
263 | sleepAfterDeadlock := func(thrId int, err error) {
264 | nDeadlocksMutex.Lock()
265 | nDeadlocks++
266 | t.Logf("thread %d operation failed: %v deadlock #%v", thrId, err, nDeadlocks)
267 | sleepTime := time.Duration(rand.Intn(int(nDeadlocks) + 1))
268 | nDeadlocksMutex.Unlock()
269 | time.Sleep(sleepTime * time.Millisecond)
270 | }
271 |
272 | incrementer := func(thrId int) {
273 | // Signal that this goroutine is ready
274 | readyWg.Done()
275 |
276 | // Wait for the signal to start
277 | <-startChan
278 |
279 | for tid := TransactionID(0); ; bp.AbortTransaction(tid) {
280 | tid = NewTID()
281 | bp.BeginTransaction(tid)
282 | iter1, err := hf.Iterator(tid)
283 | if err != nil {
284 | continue
285 | }
286 |
287 | readTup, err := iter1()
288 | if err != nil {
289 | sleepAfterDeadlock(thrId, err)
290 | continue
291 | }
292 |
293 | var writeTup = Tuple{
294 | Desc: readTup.Desc,
295 | Fields: []DBValue{
296 | readTup.Fields[0],
297 | IntField{readTup.Fields[1].(IntField).Value + 1},
298 | }}
299 |
300 | dop := NewDeleteOp(hf, hf)
301 | iterDel, err := dop.Iterator(tid)
302 | if err != nil {
303 | continue
304 | }
305 | delCnt, err := iterDel()
306 | if err != nil {
307 | sleepAfterDeadlock(thrId, err)
308 | continue
309 | }
310 | if delCnt.Fields[0].(IntField).Value != 1 {
311 | t.Errorf("Delete Op should return 1")
312 | }
313 | iop := NewInsertOp(hf, &Singleton{writeTup, false})
314 | iterIns, err := iop.Iterator(tid)
315 | if err != nil {
316 | continue
317 | }
318 | insCnt, err := iterIns()
319 | if err != nil {
320 | sleepAfterDeadlock(thrId, err)
321 | continue
322 | }
323 |
324 | if insCnt.Fields[0].(IntField).Value != 1 {
325 | t.Errorf("Insert Op should return 1")
326 | }
327 |
328 | bp.CommitTransaction(tid)
329 | break //exit on success, so we don't do terminal abort
330 | }
331 | startWg.Done()
332 | }
333 |
334 | // Prepare goroutines
335 | readyWg.Add(threads)
336 | startWg.Add(threads)
337 | for i := 0; i < threads; i++ {
338 | go incrementer(i)
339 | }
340 |
341 | // Wait for all goroutines to be ready
342 | readyWg.Wait()
343 |
344 | // Start all goroutines at once
345 | close(startChan)
346 |
347 | // Wait for all goroutines to finish
348 | startWg.Wait()
349 |
350 | tid := NewTID()
351 | bp.BeginTransaction(tid)
352 | iter, _ := hf.Iterator(tid)
353 | tup, _ := iter()
354 |
355 | diff := tup.Fields[1].(IntField).Value - t2.Fields[1].(IntField).Value
356 | if diff != int64(threads) {
357 | t.Errorf("Expected #increments = %d, found %d", threads, diff)
358 | }
359 | }
360 |
361 | func TestTransactionSingleThread(t *testing.T) {
362 | validateTransactions(t, 1)
363 | }
364 |
365 | func TestTransactionTwoThreads(t *testing.T) {
366 | validateTransactions(t, 2)
367 | }
368 |
369 | func TestTransactionFiveThreads(t *testing.T) {
370 | validateTransactions(t, 5)
371 | }
372 |
373 | func TestTransactionAllDirtyFails(t *testing.T) {
374 | if os.Getenv("LAB") == "5" {
375 | t.Skip("Test is valid up through Lab 4. Skipping.")
376 | }
377 | td, t1, _, hf, bp, tid := makeTestVars(t)
378 |
379 | for hf.NumPages() < 3 {
380 | hf.insertTuple(&t1, tid)
381 | if hf.NumPages() == 0 {
382 | t.Fatalf("Heap file should have at least one page after insertion.")
383 | }
384 | }
385 | bp.CommitTransaction(tid) // make three clean pages
386 |
387 | os.Remove(TestingFile2)
388 | hf2, _ := NewHeapFile(TestingFile2, &td, bp)
389 | tid2 := NewTID()
390 | bp.BeginTransaction(tid2)
391 |
392 | for hf2.NumPages() < 3 { // make three dirty pages
393 | hf2.insertTuple(&t1, tid2)
394 | if hf2.NumPages() == 0 {
395 | t.Fatalf("Heap file should have at least one page after insertion.")
396 | }
397 | }
398 |
399 | _, err := bp.GetPage(hf, 0, tid2, ReadPerm) // since bp capacity = 3, should return error due to all dirty pages
400 | if err == nil {
401 | t.Errorf("Expected error due to all dirty pages")
402 | }
403 | }
404 |
405 | func TestTransactionAbortEviction(t *testing.T) {
406 | tupExists := func(t0 Tuple, tid TransactionID, hf *HeapFile) (bool, error) {
407 | iter, err := hf.Iterator(tid)
408 | if err != nil {
409 | return false, err
410 | }
411 | for tup, err := iter(); tup != nil; tup, err = iter() {
412 | if err != nil {
413 | return false, err
414 | }
415 | if t0.equals(tup) {
416 | return true, nil
417 | }
418 | }
419 | return false, nil
420 | }
421 |
422 | _, t1, _, hf, bp, tid := makeTestVars(t)
423 | hf.insertTuple(&t1, tid)
424 | if exists, err := tupExists(t1, tid, hf); !(exists == true && err == nil) {
425 | t.Errorf("Tuple should exist")
426 | }
427 | bp.AbortTransaction(tid)
428 |
429 | tid2 := NewTID()
430 | bp.BeginTransaction(tid2)
431 |
432 | // tuple should not exist after abort
433 | if exists, err := tupExists(t1, tid2, hf); !(exists == false && err == nil) {
434 | t.Errorf("Tuple should not exist")
435 | }
436 | }
437 |
--------------------------------------------------------------------------------
/godb/tuple.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | //This file defines methods for working with tuples, including defining
4 | // the types DBType, FieldType, TupleDesc, DBValue, and Tuple
5 |
6 | import (
7 | "bytes"
8 | "fmt"
9 | "strconv"
10 | "strings"
11 |
12 | )
13 |
14 | // DBType is the type of a tuple field, in GoDB, e.g., IntType or StringType
15 | type DBType int
16 |
17 | const (
18 | IntType DBType = iota
19 | StringType DBType = iota
20 | UnknownType DBType = iota //used internally, during parsing, because sometimes the type is unknown
21 | )
22 |
23 | func (t DBType) String() string {
24 | switch t {
25 | case IntType:
26 | return "int"
27 | case StringType:
28 | return "string"
29 | }
30 | return "unknown"
31 | }
32 |
33 | // FieldType is the type of a field in a tuple, e.g., its name, table, and [godb.DBType].
34 | // TableQualifier may or may not be an emtpy string, depending on whether the table
35 | // was specified in the query
36 | type FieldType struct {
37 | Fname string
38 | TableQualifier string
39 | Ftype DBType
40 | }
41 |
42 | // TupleDesc is "type" of the tuple, e.g., the field names and types
43 | type TupleDesc struct {
44 | Fields []FieldType
45 | }
46 |
47 | // Compare two tuple descs, and return true iff
48 | // all of their field objects are equal and they
49 | // are the same length
50 | func (d1 *TupleDesc) equals(d2 *TupleDesc) bool {
51 | // TODO: some code goes here
52 | return true
53 |
54 | }
55 |
56 | // Given a FieldType f and a TupleDesc desc, find the best
57 | // matching field in desc for f. A match is defined as
58 | // having the same Ftype and the same name, preferring a match
59 | // with the same TableQualifier if f has a TableQualifier
60 | // We have provided this implementation because it's details are
61 | // idiosyncratic to the behavior of the parser, which we are not
62 | // asking you to write
63 | func findFieldInTd(field FieldType, desc *TupleDesc) (int, error) {
64 | best := -1
65 | for i, f := range desc.Fields {
66 | if f.Fname == field.Fname && (f.Ftype == field.Ftype || field.Ftype == UnknownType) {
67 | if field.TableQualifier == "" && best != -1 {
68 | return 0, GoDBError{AmbiguousNameError, fmt.Sprintf("select name %s is ambiguous", f.Fname)}
69 | }
70 | if f.TableQualifier == field.TableQualifier || best == -1 {
71 | best = i
72 | }
73 | }
74 | }
75 | if best != -1 {
76 | return best, nil
77 | }
78 | return -1, GoDBError{IncompatibleTypesError, fmt.Sprintf("field %s.%s not found", field.TableQualifier, field.Fname)}
79 |
80 | }
81 |
82 | // Make a copy of a tuple desc. Note that in go, assignment of a slice to
83 | // another slice object does not make a copy of the contents of the slice.
84 | // Look at the built-in function "copy".
85 | func (td *TupleDesc) copy() *TupleDesc {
86 | // TODO: some code goes here
87 | return &TupleDesc{} //replace me
88 | }
89 |
90 | // Assign the TableQualifier of every field in the TupleDesc to be the
91 | // supplied alias. We have provided this function as it is only used
92 | // by the parser.
93 | func (td *TupleDesc) setTableAlias(alias string) {
94 | fields := make([]FieldType, len(td.Fields))
95 | copy(fields, td.Fields)
96 | for i := range fields {
97 | fields[i].TableQualifier = alias
98 | }
99 | td.Fields = fields
100 | }
101 |
102 | // Merge two TupleDescs together. The resulting TupleDesc
103 | // should consist of the fields of desc2
104 | // appended onto the fields of desc.
105 | func (desc *TupleDesc) merge(desc2 *TupleDesc) *TupleDesc {
106 | // TODO: some code goes here
107 | return &TupleDesc{} //replace me
108 | }
109 |
110 | // ================== Tuple Methods ======================
111 |
112 | // Interface for tuple field values
113 | type DBValue interface {
114 | EvalPred(v DBValue, op BoolOp) bool
115 | }
116 |
117 | // Integer field value
118 | type IntField struct {
119 | Value int64
120 | }
121 |
122 | // String field value
123 | type StringField struct {
124 | Value string
125 | }
126 |
127 | // Tuple represents the contents of a tuple read from a database
128 | // It includes the tuple descriptor, and the value of the fields
129 | type Tuple struct {
130 | Desc TupleDesc
131 | Fields []DBValue
132 | Rid recordID //used to track the page and position this page was read from
133 | }
134 |
135 | type recordID interface {
136 | }
137 |
138 | // Serialize the contents of the tuple into a byte array Since all tuples are of
139 | // fixed size, this method should simply write the fields in sequential order
140 | // into the supplied buffer.
141 | //
142 | // See the function [binary.Write]. Objects should be serialized in little
143 | // endian oder.
144 | //
145 | // Strings can be converted to byte arrays by casting to []byte. Note that all
146 | // strings need to be padded to StringLength bytes (set in types.go). For
147 | // example if StringLength is set to 5, the string 'mit' should be written as
148 | // 'm', 'i', 't', 0, 0
149 | //
150 | // May return an error if the buffer has insufficient capacity to store the
151 | // tuple.
152 | func (t *Tuple) writeTo(b *bytes.Buffer) error {
153 | // TODO: some code goes here
154 | return fmt.Errorf("writeTo not implemented") //replace me
155 | }
156 |
157 | // Read the contents of a tuple with the specified [TupleDesc] from the
158 | // specified buffer, returning a Tuple.
159 | //
160 | // See [binary.Read]. Objects should be deserialized in little endian oder.
161 | //
162 | // All strings are stored as StringLength byte objects.
163 | //
164 | // Strings with length < StringLength will be padded with zeros, and these
165 | // trailing zeros should be removed from the strings. A []byte can be cast
166 | // directly to string.
167 | //
168 | // May return an error if the buffer has insufficent data to deserialize the
169 | // tuple.
170 | func readTupleFrom(b *bytes.Buffer, desc *TupleDesc) (*Tuple, error) {
171 | // TODO: some code goes here
172 | return nil, fmt.Errorf("readTupleFrom not implemented") //replace me
173 | }
174 |
175 | // Compare two tuples for equality. Equality means that the TupleDescs are equal
176 | // and all of the fields are equal. TupleDescs should be compared with
177 | // the [TupleDesc.equals] method, but fields can be compared directly with equality
178 | // operators.
179 | func (t1 *Tuple) equals(t2 *Tuple) bool {
180 | // TODO: some code goes here
181 | return true
182 | }
183 |
184 | // Merge two tuples together, producing a new tuple with the fields of t2
185 | // appended to t1. The new tuple should have a correct TupleDesc that is created
186 | // by merging the descriptions of the two input tuples.
187 | func joinTuples(t1 *Tuple, t2 *Tuple) *Tuple {
188 | // TODO: some code goes here
189 | return &Tuple{}
190 | }
191 |
192 | type orderByState int
193 |
194 | const (
195 | OrderedLessThan orderByState = iota
196 | OrderedEqual orderByState = iota
197 | OrderedGreaterThan orderByState = iota
198 | )
199 |
200 | // Apply the supplied expression to both t and t2, and compare the results,
201 | // returning an orderByState value.
202 | //
203 | // Takes an arbitrary expressions rather than a field, because, e.g., for an
204 | // ORDER BY SQL may ORDER BY arbitrary expressions, e.g., substr(name, 1, 2)
205 | //
206 | // Note that in most cases Expr will be a [godb.FieldExpr], which simply
207 | // extracts a named field from a supplied tuple.
208 | //
209 | // Calling the [Expr.EvalExpr] method on a tuple will return the value of the
210 | // expression on the supplied tuple.
211 | //
212 | // Note that EvalExpr uses the [Tuple.project] method, so you will need
213 | // to implement projection before testing compareField.
214 | func (t *Tuple) compareField(t2 *Tuple, field Expr) (orderByState, error) {
215 | // TODO: some code goes here
216 | return OrderedEqual, fmt.Errorf("compareField not implemented") // replace me
217 | }
218 |
219 | // Project out the supplied fields from the tuple. Should return a new Tuple
220 | // with just the fields named in fields.
221 | //
222 | // Should not require a match on TableQualifier, but should prefer fields that
223 | // do match on TableQualifier (e.g., a field t1.name in fields should match an
224 | // entry t2.name in t, but only if there is not an entry t1.name in t)
225 | func (t *Tuple) project(fields []FieldType) (*Tuple, error) {
226 | // TODO: some code goes here
227 | return nil, fmt.Errorf("project not implemented") //replace me
228 | }
229 |
230 | // Compute a key for the tuple to be used in a map structure
231 | func (t *Tuple) tupleKey() any {
232 | var buf bytes.Buffer
233 | t.writeTo(&buf)
234 | return buf.String()
235 | }
236 |
237 | var winWidth int = 120
238 |
239 | func fmtCol(v string, ncols int) string {
240 | colWid := winWidth / ncols
241 | nextLen := len(v) + 3
242 | remLen := colWid - nextLen
243 | if remLen > 0 {
244 | spacesRight := remLen / 2
245 | spacesLeft := remLen - spacesRight
246 | return strings.Repeat(" ", spacesLeft) + v + strings.Repeat(" ", spacesRight) + " |"
247 | } else {
248 | return " " + v[0:colWid-4] + " |"
249 | }
250 | }
251 |
252 | // Return a string representing the header of a table for a tuple with the
253 | // supplied TupleDesc.
254 | //
255 | // Aligned indicates if the tuple should be foramtted in a tabular format
256 | func (d *TupleDesc) HeaderString(aligned bool) string {
257 | outstr := ""
258 | for i, f := range d.Fields {
259 | tableName := ""
260 | if f.TableQualifier != "" {
261 | tableName = f.TableQualifier + "."
262 | }
263 |
264 | if aligned {
265 | outstr = fmt.Sprintf("%s %s", outstr, fmtCol(tableName+f.Fname, len(d.Fields)))
266 | } else {
267 | sep := ","
268 | if i == 0 {
269 | sep = ""
270 | }
271 | outstr = fmt.Sprintf("%s%s%s", outstr, sep, tableName+f.Fname)
272 | }
273 | }
274 | return outstr
275 | }
276 |
277 | // Return a string representing the tuple
278 | // Aligned indicates if the tuple should be formatted in a tabular format
279 | func (t *Tuple) PrettyPrintString(aligned bool) string {
280 | outstr := ""
281 | for i, f := range t.Fields {
282 | str := ""
283 | switch f := f.(type) {
284 | case IntField:
285 | str = strconv.FormatInt(f.Value, 10)
286 | case StringField:
287 | str = f.Value
288 | }
289 | if aligned {
290 | outstr = fmt.Sprintf("%s %s", outstr, fmtCol(str, len(t.Fields)))
291 | } else {
292 | sep := ","
293 | if i == 0 {
294 | sep = ""
295 | }
296 | outstr = fmt.Sprintf("%s%s%s", outstr, sep, str)
297 | }
298 | }
299 | return outstr
300 | }
301 |
--------------------------------------------------------------------------------
/godb/tuple_test.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "testing"
7 | )
8 |
9 | func CheckIfOutputMatches(f func() (*Tuple, error), ts []*Tuple) error {
10 | n := 0
11 | for {
12 | t1, err := f()
13 | if err != nil {
14 | return err
15 | }
16 | if t1 == nil {
17 | break
18 | }
19 |
20 | if n >= len(ts) {
21 | return fmt.Errorf("too many tuples returned. expected %d", len(ts))
22 | }
23 |
24 | t2 := ts[n]
25 | if !t1.equals(t2) {
26 | return fmt.Errorf("tuple %d did not match expected tuple. expected %v, got %v", n, t2, t1)
27 | }
28 | n++
29 | }
30 | if n < len(ts) {
31 | return fmt.Errorf("too few tuples returned. expected %d, got %d", len(ts), n)
32 | }
33 | return nil
34 | }
35 |
36 | func CheckIfOutputMatchesUnordered(f func() (*Tuple, error), ts []*Tuple) error {
37 | n := len(ts)
38 | found := make([]bool, n)
39 |
40 | i := 0
41 | for {
42 | t1, err := f()
43 | if err != nil {
44 | return err
45 | }
46 | if t1 == nil {
47 | break
48 | }
49 |
50 | if i >= n {
51 | return fmt.Errorf("too many tuples returned. expected %d", n)
52 | }
53 |
54 | found_this := false
55 | for j, t2 := range ts {
56 | if !found[j] && t1.equals(t2) {
57 | found[j] = true
58 | found_this = true
59 | break
60 | }
61 | }
62 |
63 | if !found_this {
64 | return fmt.Errorf("received unexpected tuple %v", t1)
65 | }
66 | i++
67 | }
68 | if i < n {
69 | return fmt.Errorf("too few tuples returned. expected %d, got %d", n, i)
70 | }
71 | for j, f := range found {
72 | if !f {
73 | return fmt.Errorf("missing tuple %v", ts[j])
74 | }
75 | }
76 | return nil
77 | }
78 |
79 | func makeTupleTestVars() (TupleDesc, Tuple, Tuple) {
80 | var td = TupleDesc{Fields: []FieldType{
81 | {Fname: "name", Ftype: StringType},
82 | {Fname: "age", Ftype: IntType},
83 | }}
84 |
85 | var t1 = Tuple{
86 | Desc: td,
87 | Fields: []DBValue{
88 | StringField{"sam"},
89 | IntField{25},
90 | }}
91 |
92 | var t2 = Tuple{
93 | Desc: td,
94 | Fields: []DBValue{
95 | StringField{"george jones"},
96 | IntField{999},
97 | }}
98 |
99 | return td, t1, t2
100 | }
101 |
102 | // Unit test for Tuple.writeTo() and Tuple.readTupleFrom()
103 | func TestTupleSerialization(t *testing.T) {
104 | td, t1, _ := makeTupleTestVars()
105 | b := new(bytes.Buffer)
106 | t1.writeTo(b)
107 | t3, err := readTupleFrom(b, &td)
108 | if err != nil {
109 | t.Fatalf("Error loading tuple from saved buffer: %v", err.Error())
110 | }
111 | if !t3.equals(&t1) {
112 | t.Errorf("Serialization / deserialization doesn't result in identical tuple.")
113 | }
114 | }
115 |
116 | // Unit test for Tuple.compareField()
117 | func TestTupleExpr(t *testing.T) {
118 | td, t1, t2 := makeTupleTestVars()
119 | ft := td.Fields[0]
120 | f := FieldExpr{ft}
121 | result, err := t1.compareField(&t2, &f) // compare "sam" to "george jones"
122 | if err != nil {
123 | t.Fatalf(err.Error())
124 | }
125 | if result != OrderedGreaterThan {
126 | t.Errorf("comparison of fields did not return expected result")
127 | }
128 | }
129 |
130 | // Unit test for Tuple.project()
131 | func TestTupleProject(t *testing.T) {
132 | _, t1, _ := makeTupleTestVars()
133 | tNew, err := t1.project([]FieldType{t1.Desc.Fields[0]})
134 | if err != nil {
135 | t.Fatalf(err.Error())
136 | }
137 | if tNew == nil {
138 | t.Fatalf("new tuple was nil")
139 | }
140 | if len(tNew.Fields) != 1 {
141 | t.Fatalf("unexpected number of fields after project")
142 | }
143 | f, ok := tNew.Fields[0].(StringField)
144 | if !ok || f.Value != "sam" {
145 | t.Errorf("unexpected value after project")
146 | }
147 | }
148 |
149 | // Unit test for Tuple.project()
150 | func TestTupleProjectQualifier(t *testing.T) {
151 | td1 := TupleDesc{Fields: []FieldType{{Fname: "f", TableQualifier: "t1", Ftype: IntType}, {Fname: "f", TableQualifier: "t2", Ftype: IntType}}}
152 | t1 := Tuple{td1, []DBValue{IntField{1}, IntField{2}}, nil}
153 |
154 | tNew, err := t1.project([]FieldType{t1.Desc.Fields[1]})
155 | if err != nil {
156 | t.Fatalf(err.Error())
157 | }
158 | if tNew == nil {
159 | t.Fatalf("new tuple was nil")
160 | }
161 | if len(tNew.Fields) != 1 {
162 | t.Fatalf("unexpected number of fields after project")
163 | }
164 | f, ok := tNew.Fields[0].(IntField)
165 | if !ok || f.Value != 2 {
166 | t.Errorf("failed to select t2.f")
167 | }
168 |
169 | td2 := TupleDesc{Fields: []FieldType{{Fname: "g", TableQualifier: "t1", Ftype: IntType}, {Fname: "f", TableQualifier: "t2", Ftype: IntType}}}
170 | t2 := Tuple{td2, []DBValue{IntField{1}, IntField{2}}, nil}
171 |
172 | tNew, err = t2.project([]FieldType{{Fname: "f", TableQualifier: "t1", Ftype: IntType}})
173 | if err != nil {
174 | t.Fatalf(err.Error())
175 | }
176 | if tNew == nil {
177 | t.Fatalf("new tuple was nil")
178 | }
179 | if len(tNew.Fields) != 1 {
180 | t.Fatalf("unexpected number of fields after project")
181 | }
182 | f, ok = tNew.Fields[0].(IntField)
183 | if !ok || f.Value != 2 {
184 | t.Errorf("failed to select t2.f")
185 | }
186 | }
187 |
188 | // Unit test for Tuple.joinTuples()
189 | func TestTupleJoin(t *testing.T) {
190 | _, t1, t2 := makeTupleTestVars()
191 | tNew := joinTuples(&t1, &t2)
192 | if len(tNew.Fields) != 4 {
193 | t.Fatalf("unexpected number of fields after join")
194 | }
195 | if len(tNew.Desc.Fields) != 4 {
196 | t.Fatalf("unexpected number of fields in description after join")
197 | }
198 | f, ok := tNew.Fields[0].(StringField)
199 | if !ok || f.Value != "sam" {
200 | t.Fatalf("unexpected value after join")
201 | }
202 | f, ok = tNew.Fields[2].(StringField)
203 | if !ok || f.Value != "george jones" {
204 | t.Errorf("unexpected value after join")
205 | }
206 |
207 | }
208 |
209 | func TDAssertEquals(t *testing.T, expected, actual TupleDesc) {
210 | if !(expected.equals(&actual)) {
211 | t.Errorf("Expected EQUAL, found NOT EQUAL")
212 | }
213 | }
214 |
215 | func TDAssertNotEquals(t *testing.T, expected, actual TupleDesc) {
216 | if expected.equals(&actual) {
217 | t.Errorf("Expected EQUAL, found NOT EQUAL")
218 | }
219 | }
220 |
221 | func TAssertEquals(t *testing.T, expected, actual Tuple) {
222 | if !(expected.equals(&actual)) {
223 | t.Errorf("Expected EQUAL, found NOT EQUAL")
224 | }
225 | }
226 |
227 | func TAssertNotEquals(t *testing.T, expected, actual Tuple) {
228 | if expected.equals(&actual) {
229 | t.Errorf("Expected NOT EQUAL, found EQUAL")
230 | }
231 | }
232 |
233 | func TestTupleDescEquals(t *testing.T) {
234 | singleInt := TupleDesc{Fields: []FieldType{{Ftype: IntType}}}
235 | singleInt2 := TupleDesc{Fields: []FieldType{{Ftype: IntType}}}
236 | intString := TupleDesc{Fields: []FieldType{{Ftype: IntType}, {Ftype: StringType}}}
237 | intString2 := TupleDesc{Fields: []FieldType{{Ftype: IntType}, {Ftype: StringType}}}
238 |
239 | TDAssertEquals(t, singleInt, singleInt)
240 | TDAssertEquals(t, singleInt, singleInt2)
241 | TDAssertEquals(t, singleInt2, singleInt)
242 | TDAssertEquals(t, intString, intString)
243 |
244 | TDAssertNotEquals(t, singleInt, intString)
245 | TDAssertNotEquals(t, singleInt2, intString)
246 | TDAssertNotEquals(t, intString, singleInt)
247 | TDAssertNotEquals(t, intString, singleInt2)
248 | TDAssertEquals(t, intString, intString2)
249 | TDAssertEquals(t, intString2, intString)
250 |
251 | stringInt := TupleDesc{Fields: []FieldType{{Ftype: StringType}, {Ftype: IntType}}}
252 | _, t1, _ := makeTupleTestVars()
253 | TDAssertNotEquals(t, t1.Desc, stringInt) // diff in only Fname
254 | }
255 |
256 | // Unit test for TupleDesc.copy()
257 | func TestTupleDescCopy(t *testing.T) {
258 | singleInt := TupleDesc{Fields: []FieldType{{Ftype: IntType}}}
259 | intString := TupleDesc{Fields: []FieldType{{Ftype: IntType}, {Ftype: StringType}}}
260 |
261 | TDAssertEquals(t, singleInt, *singleInt.copy())
262 | TDAssertEquals(t, intString, *intString.copy())
263 | TDAssertEquals(t, *intString.copy(), *intString.copy())
264 | TDAssertNotEquals(t, *intString.copy(), *singleInt.copy())
265 |
266 | // tests deep copy
267 | tdCpy := intString.copy()
268 | tdCpy2 := tdCpy.copy()
269 | if tdCpy == nil || len(tdCpy.Fields) == 0 {
270 | t.Fatalf("tdCpy is nil or fields are empty")
271 | }
272 | if tdCpy2 == nil || len(tdCpy2.Fields) == 0 {
273 | t.Fatalf("tdCpy2 is nil or fields are empty")
274 | }
275 | tdCpy.Fields[0] = intString.Fields[1]
276 | TDAssertNotEquals(t, *tdCpy, *tdCpy2)
277 | tdCpy.Fields[0] = intString.Fields[0]
278 | TDAssertEquals(t, *tdCpy, *tdCpy2)
279 | }
280 |
281 | // Unit test for TupleDesc.merge()
282 | func TestTupleDescMerge(t *testing.T) {
283 | singleInt := TupleDesc{Fields: []FieldType{{Ftype: IntType}}}
284 | stringInt := TupleDesc{Fields: []FieldType{{Ftype: StringType}, {Ftype: IntType}}}
285 | td1, td2 := stringInt, stringInt.copy()
286 |
287 | tdNew := td1.merge(&singleInt).merge(td2)
288 | final := TupleDesc{Fields: []FieldType{{Ftype: StringType}, {Ftype: IntType}, {Ftype: IntType}, {Ftype: StringType}, {Ftype: IntType}}}
289 |
290 | TDAssertEquals(t, final, *tdNew)
291 | TDAssertNotEquals(t, td1, *tdNew)
292 | }
293 |
294 | // Unit test for Tuple.equals()
295 | func TestTupleEquals(t *testing.T) {
296 | _, t1, t2 := makeTupleTestVars()
297 | _, t1Dup, _ := makeTupleTestVars()
298 |
299 | var stringTup = Tuple{
300 | Desc: TupleDesc{Fields: []FieldType{{Ftype: StringType}}},
301 | Fields: []DBValue{
302 | StringField{"sam"},
303 | },
304 | }
305 |
306 | TAssertEquals(t, t1, t1)
307 | TAssertEquals(t, t1, t1Dup)
308 |
309 | TAssertNotEquals(t, t1, t2)
310 | TAssertNotEquals(t, t1, stringTup)
311 | TAssertNotEquals(t, stringTup, t2)
312 | }
313 |
314 | func TestJoinTuplesDesc(t *testing.T) {
315 | _, t1, t2 := makeTupleTestVars()
316 | tNew := joinTuples(&t1, &t2)
317 | if len(tNew.Desc.Fields) != 4 {
318 | t.Fatalf("Expected 4 fields in desc after join")
319 | }
320 | fields := []string{"name", "age", "name", "age"}
321 | for i, fname := range fields {
322 | if tNew.Desc.Fields[i].Fname != fname {
323 | t.Fatalf("expected %dth field to be named %s", i, fname)
324 | }
325 | }
326 | }
327 |
328 | func TestTupleJoinDesc(t *testing.T) {
329 | var td1 = TupleDesc{Fields: []FieldType{
330 | {Fname: "name", Ftype: StringType},
331 | {Fname: "age", Ftype: IntType},
332 | }}
333 |
334 | var td2 = TupleDesc{Fields: []FieldType{
335 | {Fname: "age2", Ftype: IntType},
336 | {Fname: "name2", Ftype: StringType},
337 | }}
338 |
339 | var t1 = Tuple{
340 | Desc: td1,
341 | Fields: []DBValue{
342 | StringField{"sam"},
343 | IntField{25},
344 | }}
345 |
346 | var t2 = Tuple{
347 | Desc: td2,
348 | Fields: []DBValue{
349 | IntField{999},
350 | StringField{"george jones"},
351 | }}
352 |
353 | tNew := joinTuples(&t1, &t2)
354 | if len(tNew.Desc.Fields) != 4 {
355 | t.Fatalf("unexpected number of desc fields after join")
356 | }
357 |
358 | var tdAns = TupleDesc{Fields: []FieldType{
359 | {Fname: "name", Ftype: StringType},
360 | {Fname: "age", Ftype: IntType},
361 | {Fname: "age2", Ftype: IntType},
362 | {Fname: "name2", Ftype: StringType},
363 | }}
364 |
365 | if !tNew.Desc.equals(&tdAns) {
366 | t.Fatalf("unexpected desc after join")
367 | }
368 | }
369 |
370 | func TestTupleProject2(t *testing.T) {
371 | var td = TupleDesc{Fields: []FieldType{
372 | {Fname: "name1", TableQualifier: "tq1", Ftype: StringType},
373 | {Fname: "name2", TableQualifier: "tq2", Ftype: StringType},
374 | {Fname: "name1", TableQualifier: "tq2", Ftype: StringType},
375 | }}
376 |
377 | var t1 = Tuple{
378 | Desc: td,
379 | Fields: []DBValue{
380 | StringField{"SFname1tq1"},
381 | StringField{"SFname2tq2"},
382 | StringField{"SFname1tq2"},
383 | }}
384 |
385 | t2, err := t1.project([]FieldType{
386 | {Fname: "name1", TableQualifier: "tq1", Ftype: StringType},
387 | {Fname: "name2", TableQualifier: "", Ftype: StringType},
388 | {Fname: "name1", TableQualifier: "tq1", Ftype: StringType},
389 | {Fname: "name2", TableQualifier: "tq2", Ftype: StringType},
390 | {Fname: "name1", TableQualifier: "tq2", Ftype: StringType},
391 | })
392 |
393 | if err != nil {
394 | t.Fatalf(err.Error())
395 | }
396 |
397 | if t2.Fields[0].(StringField).Value != "SFname1tq1" {
398 | t.Errorf("wrong match 0")
399 | }
400 | if t2.Fields[1].(StringField).Value != "SFname2tq2" {
401 | t.Errorf("wrong match 1")
402 | }
403 | if t2.Fields[2].(StringField).Value != "SFname1tq1" {
404 | t.Errorf("wrong match 2")
405 | }
406 | if t2.Fields[3].(StringField).Value != "SFname2tq2" {
407 | t.Errorf("wrong match 3")
408 | }
409 | if t2.Fields[4].(StringField).Value != "SFname1tq2" {
410 | t.Errorf("wrong match 4")
411 | }
412 | }
413 |
414 | func TestTupleProject3(t *testing.T) {
415 | td1 := TupleDesc{Fields: []FieldType{
416 | {Fname: "a", Ftype: StringType},
417 | {Fname: "b", Ftype: IntType},
418 | }}
419 |
420 | t1 := Tuple{
421 | Desc: td1,
422 | Fields: []DBValue{
423 | StringField{"sam"},
424 | IntField{25},
425 | }}
426 |
427 | ft1 := FieldType{"a", "", StringType}
428 | ft2 := FieldType{"b", "", IntType}
429 | outTup, err := t1.project([]FieldType{ft1})
430 | if err != nil {
431 | t.Fatalf(err.Error())
432 | }
433 | if (len(outTup.Fields)) != 1 {
434 | t.Fatalf("project returned %d fields, expected 1", len(outTup.Fields))
435 | }
436 | v, ok := outTup.Fields[0].(StringField)
437 |
438 | if !ok {
439 | t.Fatalf("project of name didn't return string")
440 | }
441 | if v.Value != "sam" {
442 | t.Fatalf("project didn't return sam")
443 |
444 | }
445 | outTup, _ = t1.project([]FieldType{ft2})
446 | if (len(outTup.Fields)) != 1 {
447 | t.Fatalf("project returned %d fields, expected 1", len(outTup.Fields))
448 | }
449 | v2, ok := outTup.Fields[0].(IntField)
450 |
451 | if !ok {
452 | t.Fatalf("project of name didn't return int")
453 | }
454 | if v2.Value != 25 {
455 | t.Fatalf("project didn't return 25")
456 | }
457 |
458 | outTup, _ = t1.project([]FieldType{ft2, ft1})
459 | if (len(outTup.Fields)) != 2 {
460 | t.Fatalf("project returned %d fields, expected 2", len(outTup.Fields))
461 | }
462 | v, ok = outTup.Fields[1].(StringField)
463 | if !ok {
464 | t.Fatalf("project of name didn't return string in second field")
465 | }
466 | if v.Value != "sam" {
467 | t.Fatalf("project didn't return sam")
468 |
469 | }
470 |
471 | v2, ok = outTup.Fields[0].(IntField)
472 | if !ok {
473 | t.Fatalf("project of name didn't return int in first field")
474 | }
475 | if v2.Value != 25 {
476 | t.Fatalf("project didn't return 25")
477 | }
478 | }
479 |
480 | func TestTupleJoinNil(t *testing.T) {
481 | _, t1, t2 := makeTupleTestVars()
482 | tNew := joinTuples(&t1, nil)
483 | if !tNew.equals(&t1) {
484 | t.Fatalf("Unexpected output of joinTuple with nil")
485 | }
486 | if tNew.equals(&t2) {
487 | t.Fatalf("Unexpected output of joinTuple with nil")
488 | }
489 | tNew2 := joinTuples(nil, &t2)
490 | if !tNew2.equals(&t2) {
491 | t.Fatalf("Unexpected output of joinTuple with nil")
492 | }
493 | if tNew2.equals(&t1) {
494 | t.Fatalf("Unexpected output of joinTuple with nil")
495 | }
496 | }
497 |
498 | func TestTupleJoinDesc2(t *testing.T) {
499 | _, t1, t2 := makeTupleTestVars()
500 | tNew := joinTuples(&t1, &t2)
501 | if len(tNew.Desc.Fields) != 4 {
502 | t.Fatalf("Expected 4 fields in desc after join")
503 | }
504 | fields := []string{"name", "age", "name", "age"}
505 | for i, fname := range fields {
506 | if tNew.Desc.Fields[i].Fname != fname {
507 | t.Fatalf("expected %dth field to be named %s", i, fname)
508 | }
509 | }
510 | }
511 |
--------------------------------------------------------------------------------
/godb/txn_test_1_1.csv:
--------------------------------------------------------------------------------
1 | george jones,999
--------------------------------------------------------------------------------
/godb/txn_test_300_3.csv:
--------------------------------------------------------------------------------
1 | george jones,999
2 | george jones,999
3 | george jones,999
4 | george jones,999
5 | george jones,999
6 | george jones,999
7 | george jones,999
8 | george jones,999
9 | george jones,999
10 | george jones,999
11 | george jones,999
12 | george jones,999
13 | george jones,999
14 | george jones,999
15 | george jones,999
16 | george jones,999
17 | george jones,999
18 | george jones,999
19 | george jones,999
20 | george jones,999
21 | george jones,999
22 | george jones,999
23 | george jones,999
24 | george jones,999
25 | george jones,999
26 | george jones,999
27 | george jones,999
28 | george jones,999
29 | george jones,999
30 | george jones,999
31 | george jones,999
32 | george jones,999
33 | george jones,999
34 | george jones,999
35 | george jones,999
36 | george jones,999
37 | george jones,999
38 | george jones,999
39 | george jones,999
40 | george jones,999
41 | george jones,999
42 | george jones,999
43 | george jones,999
44 | george jones,999
45 | george jones,999
46 | george jones,999
47 | george jones,999
48 | george jones,999
49 | george jones,999
50 | george jones,999
51 | george jones,999
52 | george jones,999
53 | george jones,999
54 | george jones,999
55 | george jones,999
56 | george jones,999
57 | george jones,999
58 | george jones,999
59 | george jones,999
60 | george jones,999
61 | george jones,999
62 | george jones,999
63 | george jones,999
64 | george jones,999
65 | george jones,999
66 | george jones,999
67 | george jones,999
68 | george jones,999
69 | george jones,999
70 | george jones,999
71 | george jones,999
72 | george jones,999
73 | george jones,999
74 | george jones,999
75 | george jones,999
76 | george jones,999
77 | george jones,999
78 | george jones,999
79 | george jones,999
80 | george jones,999
81 | george jones,999
82 | george jones,999
83 | george jones,999
84 | george jones,999
85 | george jones,999
86 | george jones,999
87 | george jones,999
88 | george jones,999
89 | george jones,999
90 | george jones,999
91 | george jones,999
92 | george jones,999
93 | george jones,999
94 | george jones,999
95 | george jones,999
96 | george jones,999
97 | george jones,999
98 | george jones,999
99 | george jones,999
100 | george jones,999
101 | george jones,999
102 | george jones,999
103 | george jones,999
104 | george jones,999
105 | george jones,999
106 | george jones,999
107 | george jones,999
108 | george jones,999
109 | george jones,999
110 | george jones,999
111 | george jones,999
112 | george jones,999
113 | george jones,999
114 | george jones,999
115 | george jones,999
116 | george jones,999
117 | george jones,999
118 | george jones,999
119 | george jones,999
120 | george jones,999
121 | george jones,999
122 | george jones,999
123 | george jones,999
124 | george jones,999
125 | george jones,999
126 | george jones,999
127 | george jones,999
128 | george jones,999
129 | george jones,999
130 | george jones,999
131 | george jones,999
132 | george jones,999
133 | george jones,999
134 | george jones,999
135 | george jones,999
136 | george jones,999
137 | george jones,999
138 | george jones,999
139 | george jones,999
140 | george jones,999
141 | george jones,999
142 | george jones,999
143 | george jones,999
144 | george jones,999
145 | george jones,999
146 | george jones,999
147 | george jones,999
148 | george jones,999
149 | george jones,999
150 | george jones,999
151 | george jones,999
152 | george jones,999
153 | george jones,999
154 | george jones,999
155 | george jones,999
156 | george jones,999
157 | george jones,999
158 | george jones,999
159 | george jones,999
160 | george jones,999
161 | george jones,999
162 | george jones,999
163 | george jones,999
164 | george jones,999
165 | george jones,999
166 | george jones,999
167 | george jones,999
168 | george jones,999
169 | george jones,999
170 | george jones,999
171 | george jones,999
172 | george jones,999
173 | george jones,999
174 | george jones,999
175 | george jones,999
176 | george jones,999
177 | george jones,999
178 | george jones,999
179 | george jones,999
180 | george jones,999
181 | george jones,999
182 | george jones,999
183 | george jones,999
184 | george jones,999
185 | george jones,999
186 | george jones,999
187 | george jones,999
188 | george jones,999
189 | george jones,999
190 | george jones,999
191 | george jones,999
192 | george jones,999
193 | george jones,999
194 | george jones,999
195 | george jones,999
196 | george jones,999
197 | george jones,999
198 | george jones,999
199 | george jones,999
200 | george jones,999
201 | george jones,999
202 | george jones,999
203 | george jones,999
204 | george jones,999
205 | george jones,999
206 | george jones,999
207 | george jones,999
208 | george jones,999
209 | george jones,999
210 | george jones,999
211 | george jones,999
212 | george jones,999
213 | george jones,999
214 | george jones,999
215 | george jones,999
216 | george jones,999
217 | george jones,999
218 | george jones,999
219 | george jones,999
220 | george jones,999
221 | george jones,999
222 | george jones,999
223 | george jones,999
224 | george jones,999
225 | george jones,999
226 | george jones,999
227 | george jones,999
228 | george jones,999
229 | george jones,999
230 | george jones,999
231 | george jones,999
232 | george jones,999
233 | george jones,999
234 | george jones,999
235 | george jones,999
236 | george jones,999
237 | george jones,999
238 | george jones,999
239 | george jones,999
240 | george jones,999
241 | george jones,999
242 | george jones,999
243 | george jones,999
244 | george jones,999
245 | george jones,999
246 | george jones,999
247 | george jones,999
248 | george jones,999
249 | george jones,999
250 | george jones,999
251 | george jones,999
252 | george jones,999
253 | george jones,999
254 | george jones,999
255 | george jones,999
256 | george jones,999
257 | george jones,999
258 | george jones,999
259 | george jones,999
260 | george jones,999
261 | george jones,999
262 | george jones,999
263 | george jones,999
264 | george jones,999
265 | george jones,999
266 | george jones,999
267 | george jones,999
268 | george jones,999
269 | george jones,999
270 | george jones,999
271 | george jones,999
272 | george jones,999
273 | george jones,999
274 | george jones,999
275 | george jones,999
276 | george jones,999
277 | george jones,999
278 | george jones,999
279 | george jones,999
280 | george jones,999
281 | george jones,999
282 | george jones,999
283 | george jones,999
284 | george jones,999
285 | george jones,999
286 | george jones,999
287 | george jones,999
288 | george jones,999
289 | george jones,999
290 | george jones,999
291 | george jones,999
292 | george jones,999
293 | george jones,999
294 | george jones,999
295 | george jones,999
296 | george jones,999
297 | george jones,999
298 | george jones,999
299 | george jones,999
300 | george jones,999
--------------------------------------------------------------------------------
/godb/types.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 | "strings"
7 | )
8 |
9 | type GoDBErrorCode int
10 |
11 | const (
12 | TupleNotFoundError GoDBErrorCode = iota
13 | PageFullError GoDBErrorCode = iota
14 | IncompatibleTypesError GoDBErrorCode = iota
15 | TypeMismatchError GoDBErrorCode = iota
16 | MalformedDataError GoDBErrorCode = iota
17 | BufferPoolFullError GoDBErrorCode = iota
18 | ParseError GoDBErrorCode = iota
19 | DuplicateTableError GoDBErrorCode = iota
20 | NoSuchTableError GoDBErrorCode = iota
21 | AmbiguousNameError GoDBErrorCode = iota
22 | IllegalOperationError GoDBErrorCode = iota
23 | DeadlockError GoDBErrorCode = iota
24 | IllegalTransactionError GoDBErrorCode = iota
25 | )
26 |
27 | //go:generate stringer -type=GoDBErrorCode
28 |
29 | type GoDBError struct {
30 | code GoDBErrorCode
31 | errString string
32 | }
33 |
34 | func (e GoDBError) Error() string {
35 | return fmt.Sprintf("err: %s; msg: %s", e.code.String(), e.errString)
36 | }
37 |
38 | const (
39 | PageSize int = 4096
40 | StringLength int = 32
41 | )
42 |
43 | type Page interface {
44 | // these methods are used by buffer pool to manage pages
45 | isDirty() bool
46 | setDirty(tid TransactionID, dirty bool)
47 | getFile() DBFile
48 | }
49 |
50 | type DBFile interface {
51 | insertTuple(t *Tuple, tid TransactionID) error
52 | deleteTuple(t *Tuple, tid TransactionID) error
53 |
54 | // methods used by buffer pool to manage retrieval of pages
55 | readPage(pageNo int) (Page, error)
56 | flushPage(page Page) error
57 | pageKey(pgNo int) any //uint64
58 |
59 | NumPages() int
60 |
61 | Operator
62 | }
63 |
64 | type Operator interface {
65 | Descriptor() *TupleDesc
66 | Iterator(tid TransactionID) (func() (*Tuple, error), error)
67 | }
68 |
69 | type BoolOp int
70 |
71 | const (
72 | OpGt BoolOp = iota
73 | OpLt BoolOp = iota
74 | OpGe BoolOp = iota
75 | OpLe BoolOp = iota
76 | OpEq BoolOp = iota
77 | OpNeq BoolOp = iota
78 | OpLike BoolOp = iota
79 | )
80 |
81 | var BoolOpMap = map[string]BoolOp{
82 | ">": OpGt,
83 | "<": OpLt,
84 | "<=": OpLe,
85 | ">=": OpGe,
86 | "=": OpEq,
87 | "<>": OpNeq,
88 | "!=": OpNeq,
89 | "like": OpLike,
90 | }
91 |
92 | func (i1 IntField) EvalPred(v2 DBValue, op BoolOp) bool {
93 | i2, ok := v2.(IntField)
94 | if !ok {
95 | return false
96 | }
97 | x1 := i1.Value
98 | x2 := i2.Value
99 | switch op {
100 | case OpEq:
101 | return x1 == x2
102 | case OpNeq:
103 | return x1 != x2
104 | case OpGt:
105 | return x1 > x2
106 | case OpGe:
107 | return x1 >= x2
108 | case OpLt:
109 | return x1 < x2
110 | case OpLe:
111 | return x1 <= x2
112 | default:
113 | return false
114 | }
115 | }
116 |
117 | func (i1 StringField) EvalPred(v2 DBValue, op BoolOp) bool {
118 | i2, ok := v2.(StringField)
119 | if !ok {
120 | return false
121 | }
122 | x1 := i1.Value
123 | x2 := i2.Value
124 | switch op {
125 | case OpEq:
126 | return x1 == x2
127 | case OpNeq:
128 | return x1 != x2
129 | case OpGt:
130 | return x1 > x2
131 | case OpGe:
132 | return x1 >= x2
133 | case OpLt:
134 | return x1 < x2
135 | case OpLe:
136 | return x1 <= x2
137 | case OpLike:
138 | s1, ok := any(i1).(string)
139 | if !ok {
140 | return false
141 | }
142 | regex, ok := any(i2).(string)
143 | if !ok {
144 | return false
145 | }
146 | regex = "^" + regex + "$"
147 | regex = strings.Replace(regex, "%", ".*?", -1)
148 | match, _ := regexp.MatchString(regex, s1)
149 | return match
150 | default:
151 | return false
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/godb/value_op.go:
--------------------------------------------------------------------------------
1 | package godb
2 |
3 | // Methods to expose an array of constant expressions as tuples to iterate
4 | // through (e.g., for insert statements or select from a constant list).
5 |
6 | type ValueOp struct {
7 | td *TupleDesc
8 | exprs []([]Expr)
9 | }
10 |
11 | func NewValueOp(exprs []([]Expr)) *ValueOp {
12 | var td TupleDesc
13 | if len(exprs) > 0 {
14 | first := exprs[0]
15 | fts := make([]FieldType, len(first))
16 | for i, field := range first {
17 | fts[i] = field.GetExprType()
18 | }
19 | td = TupleDesc{fts}
20 | }
21 |
22 | return &ValueOp{&td, exprs}
23 | }
24 |
25 | func (v *ValueOp) Descriptor() *TupleDesc {
26 | return v.td
27 | }
28 |
29 | func (v *ValueOp) Iterator(tid TransactionID) (func() (*Tuple, error), error) {
30 | curTup := 0
31 | return func() (*Tuple, error) {
32 | if curTup >= len(v.exprs) {
33 | return nil, nil
34 | }
35 | tup := v.exprs[curTup]
36 | fields := make([]DBValue, len(tup))
37 | for i, field := range tup {
38 | outf, err := field.EvalExpr(nil)
39 | if err != nil {
40 | return nil, err
41 | }
42 | fields[i] = outf
43 | }
44 |
45 | curTup++
46 |
47 | return &Tuple{*v.td, fields, nil}, nil
48 | }, nil
49 | }
50 |
--------------------------------------------------------------------------------