├── .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 | --------------------------------------------------------------------------------