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

Baseline Django Field Tests #62

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
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
129 changes: 129 additions & 0 deletions demo/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import uuid

from django.db import models


LOGFILE_DIR = "./logs/"


class Person(models.Model):
"""Simplest possible model utilizing one field."""

name = models.CharField(max_length=255)


class Country(models.Model):
"""Alternative Autofield test."""

country_id = models.SmallAutoField(primary_key=True)
country_code = models.CharField(max_length=255)


class Recipe(models.Model):
"""Moderately complex model utilizing the most common fields.

Tests the following fields:
- AutoField
- BooleanField
- CharField
- DateField
- DateTimeField
- DecimalField
- EmailField
- FloatField
- IntegerField
- TextField
- TimeField
"""

id = models.AutoField(primary_key=True)

archived = models.BooleanField(blank=False)

name = models.CharField(max_length=255, blank=False)
author = models.EmailField(blank=False)

created_date = models.DateField(auto_now_add=True, blank=False)
created_time = models.TimeField(auto_now_add=True, blank=False)
last_modified = models.DateTimeField(auto_now=True, blank=False)

ingredients_cost = models.DecimalField(max_digits=5, decimal_places=2)
ingredients_weight = models.FloatField()
ingredients_count = models.IntegerField()
instructions = models.TextField()


# Models with foreign relations
class Address(models.Model):
address = models.CharField(max_length=255)


class Library(models.Model):
address = models.OneToOneField(
Address,
on_delete=models.CASCADE,
primary_key=True
)


class Author(models.Model):
name = models.CharField(max_length=255, unique=True)


class Book(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey(
Author,
on_delete=models.CASCADE
)
libraries = models.ManyToManyField(Library)


class BenchmarkResults(models.Model):
"""Model utilizing lesser used specialty fields.

Tests the following fields:
- BigAutoField
- BigIntegerField
- BinaryField
- DurationField
- FileField
- FileField and FieldFile
- FilePathField
- GenericIPAddressField
- ImageField
- JSONField
- PositiveBigIntegerField
- PositiveIntegerField
- PositiveSmallIntegerField
- SlugField
- SmallIntegerField
- URLField
- UUIDField

Does NOT test:
- SmallAutoField (models cannot have multiple AutoFields)
"""

benchmark_id = models.BigAutoField(primary_key=True)
checksum = models.BinaryField()

runtime = models.DurationField(blank=False)
seed = models.BigIntegerField(blank=False)

log = models.FileField(upload_to=LOGFILE_DIR)
log_path = models.FilePathField(path=LOGFILE_DIR)

uploader_ip = models.GenericIPAddressField()
uploader_avatar = models.ImageField()
uploader_website = models.URLField()
configuration = models.JSONField()

runtime_nanoseconds = models.PositiveBigIntegerField()
runtime_milliseconds = models.PositiveIntegerField()
runtime_seconds = models.PositiveSmallIntegerField()
runtime_hours = models.SmallIntegerField()

slug = models.SlugField()

uuid = models.UUIDField(default=uuid.uuid4)
6 changes: 3 additions & 3 deletions demo/settings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from os import path

from cassandra import ConsistencyLevel

DEBUG = True
Expand All @@ -8,7 +7,10 @@

ALLOWED_HOSTS = [ "*" ]

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

INSTALLED_APPS = (
"demo",
"django_scylla",
"django.contrib.admin",
"django.contrib.auth",
Expand Down Expand Up @@ -39,8 +41,6 @@
},
}

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

MIDDLEWARE = [
# default django middleware
"django.contrib.sessions.middleware.SessionMiddleware",
Expand Down
9 changes: 4 additions & 5 deletions django_scylla/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,13 @@ class DatabaseWrapper(BaseDatabaseWrapper):
"OneToOneField": "bigint",
"PositiveBigIntegerField": "bigint",
"PositiveIntegerField": "bigint",
"PositiveSmallIntegerField": "int",
"RelatedField": "bigint",
"PositiveSmallIntegerField": "smallint",
"SlugField": "text",
"SmallAutoField": "int",
"SmallIntegerField": "int",
"SmallAutoField": "bigint",
"SmallIntegerField": "smallint",
"TextField": "text",
"TimeField": "time",
"UUIDField": "uuid",
"UUIDField": "uuid"
}

operators = {
Expand Down
40 changes: 36 additions & 4 deletions django_scylla/cql/compiler.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
from time import time
from random import SystemRandom
from os import getpid

from django.core.exceptions import EmptyResultSet
from django.db import NotSupportedError
from django.db.models import AutoField
from django.db.models import AutoField, BinaryField, DecimalField, DurationField
from django.db.models.sql import compiler

from datetime import timedelta
from cassandra.util import Duration

def unique_rowid():
# TODO: guarantee that this is globally unique
return int(time() * 1e6)

def unique_rowid(counter={'value': 0}):
"""Attempt to return a reasonably random integer value with no collisions.

In the future, this behavior will likely be discouraged in favor of using UUIDs.
"""
counter['value'] = (counter['value'] + 1) % 1024
timestamp = int(time() * 1e6)
pid = getpid()
sys_random = SystemRandom().randint(0, 65535)

return timestamp + counter['value'] + pid + sys_random
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this approach means that given the same code running simultaneously: each worker will return a different pid, each thread of that worker will return a different counter, and each call will return a different sys_random value.

The counter in particular is worth noting because it uses a little known "pitfall" of Python, which is that default values specified as dicts are persistent between runs.

e.g.

>>> def foo(counter={'value':0}):
...   counter['value'] += 1
...   return counter['value']
...
>>> foo()
1
>>> foo()
2
>>> foo()
3
>>> foo()
4



class SQLCompiler(compiler.SQLCompiler):
Expand Down Expand Up @@ -159,6 +172,25 @@ def __init__(self, *args, **kwargs):
def prepare_value(self, field, value):
if value is None and isinstance(field, AutoField):
value = unique_rowid()
return value
if value is not None and isinstance(field, DecimalField):
# Return DecimalField values directly, as the original implementation will convert Decimal instances into strings.
return value
if value is not None and isinstance(field, BinaryField):
# Assume this value is already binary formatted and perform no pre-processing.
return value
if value is not None and isinstance(field, DurationField):
# CQL driver cannot accept timedelta objects directly, so we stringify. https://docs.scylladb.com/stable/cql/types.html#durations
if not isinstance(value, timedelta):
raise ValueError("Unexpected DurationField value! DurationField values must be timedelta objects.")

# timedelta only stores days, seconds, microseconds.
# The scylla driver's Duration only accepts months, days, nanoseconds.
_converted_seconds = value.seconds * 1e9
_converted_microseconds = value.microseconds * 1000

return Duration(days=value.days, nanoseconds=(_converted_seconds + _converted_microseconds))

return super().prepare_value(field, value)


Expand Down
Loading