Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hash support #31

Merged
merged 8 commits into from
Jun 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ If you're providing your query via stdin, quotes are **not** required, however y

### Attribute

Currently supported attributes include `name`, `size`, `mode`, `time`, and `all`.
Currently supported attributes include `name`, `size`, `time`, `hash`, `mode`.

If no attribute is provided, `all` is chosen by default.
Use `all` or `*` to choose all; if no attribute is provided, this is chosen by default.

**Examples**:

Expand Down Expand Up @@ -149,10 +149,15 @@ A single condition is made up of 3 parts: an attribute, an operator, and a value

- All basic algebraic operators: `>`, `>=`, `<`, `<=`, `=`, and `<>` / `!=`.

- `hash`:

- `=` or `<>` / `!=`

- `mode`:

- `IS`


- **Value**:

If the value contains spaces, wrap the value in quotes (either single or double) or backticks.
Expand All @@ -163,6 +168,8 @@ A single condition is made up of 3 parts: an attribute, an operator, and a value

Use `mode` to test if a file is regular (`IS REG`) or if it's a directory (`IS DIR`).

Use `hash` to compute and/or compare the hash value of a file. The default algorithm is `SHA1`

#### Conjunction / Disjunction

Use `AND` / `OR` to join conditions. Note that precedence is assigned based on order of appearance.
Expand Down Expand Up @@ -199,30 +206,43 @@ The table below lists currently-supported modifiers. Note that the first paramet

| Attribute | Modifier | Supported in `SELECT` | Supported in `WHERE` |
| :---: | --- | :---: | :---: |
| `name` | `UPPER` (synonymous to `FORMAT(, UPPER)` | ✔️ | ✔️ |
| | `LOWER` (synonymous to `FORMAT(, LOWER)` | ✔️ | ✔️ |
| `hash` | `SHA1(, n)` | ✔️ | ✔️ |
| `name` | `UPPER` (synonymous to `FORMAT(, UPPER)`) | ✔️ | ✔️ |
| | `LOWER` (synonymous to `FORMAT(, LOWER)`) | ✔️ | ✔️ |
| | `FULLPATH` | ✔️ | |
| | `SHORTPATH` | ✔️ | |
| `size` | `FORMAT(, unit)` | ✔️ | ✔️ |
| `time` | `FORMAT(, layout)` | ✔️ | ✔️ |


Supported `unit` values: `B` (byte), `KB` (kilobyte), `MB` (megabyte), or `GB` (gigabyte).
- **`n`**:

Specify the length of the hash value. Use a negative integer or `ALL` to display all digits.

- **`unit`**:

Supported `layout` values: [`ISO`](https://en.wikipedia.org/wiki/ISO_8601), [`UNIX`](https://en.wikipedia.org/wiki/Unix_time), or [custom](https://golang.org/pkg/time/#Time.Format). Custom layouts must be provided in reference to the following date: `Mon Jan 2 15:04:05 -0700 MST 2006`.
Specify the size unit. One of: `B` (byte), `KB` (kilobyte), `MB` (megabyte), or `GB` (gigabyte).

- **`layout`**:

Specify the time layout. One of: [`ISO`](https://en.wikipedia.org/wiki/ISO_8601), [`UNIX`](https://en.wikipedia.org/wiki/Unix_time), or [custom](https://golang.org/pkg/time/#Time.Format). Custom layouts must be provided in reference to the following date: `Mon Jan 2 15:04:05 -0700 MST 2006`.

**Examples**:

```console
>>> ... WHERE UPPER(name) LIKE %.go ...
>>> SELECT SHA1(hash, 20) ...
```

```console
>>> ... WHERE UPPER(name) ...
```

```console
>>> ... WHERE FORMAT(size, GB) > 2 ...
>>> SELECT FORMAT(size, MB) ...
```

```console
>>> ... SELECT FORMAT(time, "Mon Jan 2 2006 15:04:05") ...
>>> ... WHERE FORMAT(time, "Mon Jan 2 2006 15:04:05") ...
```

### Subqueries
Expand Down
150 changes: 150 additions & 0 deletions evaluate/compare.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package evaluate

import (
"fmt"
"regexp"
"strings"
"time"

"github.com/kshvmdn/fsql/tokenizer"
"github.com/kshvmdn/fsql/transform"
)

// cmpAlpha performs alphabetic comparison on a and b.
func cmpAlpha(o *Opts, a, b interface{}) (result bool, err error) {
switch o.Operator {
case tokenizer.Equals:
result = a.(string) == b.(string)
case tokenizer.NotEquals:
result = a.(string) != b.(string)
case tokenizer.Like:
aStr, bStr := a.(string), b.(string)
if strings.HasPrefix(bStr, "%") && strings.HasSuffix(bStr, "%") {
result = strings.Contains(aStr, bStr[1:len(bStr)-1])
} else if strings.HasPrefix(bStr, "%") {
result = strings.HasSuffix(aStr, bStr[1:])
} else if strings.HasSuffix(bStr, "%") {
result = strings.HasPrefix(aStr, bStr[:len(bStr)-1])
} else {
result = strings.Contains(aStr, bStr)
}
case tokenizer.RLike:
result = regexp.MustCompile(b.(string)).MatchString(a.(string))
case tokenizer.In:
switch t := b.(type) {
case map[interface{}]bool:
if _, ok := t[a.(string)]; ok {
result = true
}
case []string:
for _, el := range t {
if a.(string) == el {
result = true
}
}
case string:
for _, el := range strings.Split(t, ",") {
if a.(string) == el {
result = true
}
}
}
default:
err = &ErrUnsupportedOperator{o.Attribute, o.Operator}
}
return result, err
}

// cmpNumeric performs numeric comparison on a and b.
func cmpNumeric(o *Opts, a, b interface{}) (result bool, err error) {
switch o.Operator {
case tokenizer.Equals:
result = a.(int64) == b.(int64)
case tokenizer.NotEquals:
result = a.(int64) != b.(int64)
case tokenizer.GreaterThanEquals:
result = a.(int64) >= b.(int64)
case tokenizer.GreaterThan:
result = a.(int64) > b.(int64)
case tokenizer.LessThanEquals:
result = a.(int64) <= b.(int64)
case tokenizer.LessThan:
result = a.(int64) < b.(int64)
case tokenizer.In:
if _, ok := b.(map[interface{}]bool)[a.(int64)]; ok {
result = true
}
default:
err = &ErrUnsupportedOperator{o.Attribute, o.Operator}
}
return result, err
}

// cmpTime performs time comparison on a and b.
func cmpTime(o *Opts, a, b interface{}) (result bool, err error) {
switch o.Operator {
case tokenizer.Equals:
result = a.(time.Time).Equal(b.(time.Time))
case tokenizer.NotEquals:
result = !a.(time.Time).Equal(b.(time.Time))
case tokenizer.GreaterThanEquals:
result = a.(time.Time).After(b.(time.Time)) || a.(time.Time).Equal(b.(time.Time))
case tokenizer.GreaterThan:
result = a.(time.Time).After(b.(time.Time))
case tokenizer.LessThanEquals:
result = a.(time.Time).Before(b.(time.Time)) || a.(time.Time).Equal(b.(time.Time))
case tokenizer.LessThan:
result = a.(time.Time).Before(b.(time.Time))
case tokenizer.In:
if _, ok := b.(map[interface{}]bool)[a.(time.Time)]; ok {
result = true
}
default:
err = &ErrUnsupportedOperator{o.Attribute, o.Operator}
}
return result, err
}

// cmpMode performs mode comparison with info and typ.
func cmpMode(o *Opts) (result bool, err error) {
if o.Operator != tokenizer.Is {
return false, &ErrUnsupportedOperator{o.Attribute, o.Operator}
}
switch strings.ToUpper(o.Value.(string)) {
case "DIR":
result = o.File.Mode().IsDir()
case "REG":
result = o.File.Mode().IsRegular()
default:
result = false
}
return result, err
}

// cmpHash computes the hash of the current file and compares it with the
// provided value.
func cmpHash(o *Opts) (result bool, err error) {
hashType := "SHA1"
if len(o.Modifiers) > 0 {
hashType = o.Modifiers[0].Name
}

hashFunc := transform.FindHash(hashType)
if hashFunc == nil {
return false, fmt.Errorf("unexpected hash algorithm %s", hashType)
}
h, err := transform.ComputeHash(o.File, o.Path, hashFunc())
if err != nil {
return false, err
}

switch o.Operator {
case tokenizer.Equals:
result = h == o.Value
case tokenizer.NotEquals:
result = h != o.Value
default:
err = &ErrUnsupportedOperator{o.Attribute, o.Operator}
}
return result, err
}
Loading