|
| 1 | +import io |
1 | 2 | import uuid |
2 | 3 | import typing |
3 | 4 | from typing import Literal, Optional |
4 | | -from django.db import models |
| 5 | +from django.db import models, connection |
5 | 6 | from django.db.models import Q |
6 | 7 | from iam.models import Workspace |
7 | 8 | from iam.models.utils import PermissionChecker |
@@ -49,6 +50,34 @@ def removable(self, user: Optional["User"]): |
49 | 50 | datastream__thing__workspace__collaborators__role__permissions__permission_type__in=["*", "delete"]) |
50 | 51 | ) |
51 | 52 |
|
| 53 | + def bulk_copy(self, observations, batch_size=100_000): |
| 54 | + db_table_sql = connection.ops.quote_name(self.model._meta.db_table) # noqa |
| 55 | + db_fields = [field.column for field in self.model._meta.fields if not field.primary_key] # noqa |
| 56 | + db_fields_sql = ", ".join(connection.ops.quote_name(field) for field in db_fields) |
| 57 | + |
| 58 | + def escape_pg_copy(value): |
| 59 | + if value is None: |
| 60 | + return r"\N" |
| 61 | + if isinstance(value, str): |
| 62 | + return value.replace("\\", "\\\\").replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r") |
| 63 | + return str(value) |
| 64 | + |
| 65 | + with connection.cursor() as cursor: |
| 66 | + with cursor.copy(f"COPY {db_table_sql} ({db_fields_sql}) FROM STDIN") as copy: |
| 67 | + buffer = io.StringIO() |
| 68 | + for i in range(0, len(observations), batch_size): |
| 69 | + batch = observations[i: i + batch_size] |
| 70 | + buffer.write("\n".join( |
| 71 | + "\t".join(escape_pg_copy(getattr(obs, field, None)) for field in db_fields) |
| 72 | + for obs in batch |
| 73 | + ) + "\n") |
| 74 | + buffer.seek(0) |
| 75 | + copy.write(buffer.read()) |
| 76 | + buffer.truncate(0) |
| 77 | + buffer.seek(0) |
| 78 | + |
| 79 | + return observations |
| 80 | + |
52 | 81 |
|
53 | 82 | class Observation(models.Model, PermissionChecker): |
54 | 83 | pk = models.CompositePrimaryKey("datastream_id", "phenomenon_time", "id") |
|
0 commit comments