Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
Insert
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
6 / 6
15
100.00% covered (success)
100.00%
1 / 1
 into
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 values
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 onConflict
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 onDuplicateKeyUpdate
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 render
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
10
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Db\Sql;
16
17/**
18 * Insert class
19 *
20 * @category   Pop
21 * @package    Pop\Db
22 * @author     Nick Sagona, III <nick@popphp.org>
23 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
24 * @license    https://www.popphp.org/license     New BSD License
25 * @version    7.0.0
26 */
27class Insert extends AbstractClause
28{
29
30    /**
31     * Conflict key for UPSERT
32     * @var ?string
33     */
34    protected ?string $conflictKey = null;
35
36    /**
37     * Conflict columns for UPSERT
38     * @var array
39     */
40    protected array $conflictColumns = [];
41
42    /**
43     * Set into table
44     *
45     * @param  mixed $table
46     * @return Insert
47     */
48    public function into(mixed $table): Insert
49    {
50        $this->setTable($table);
51        return $this;
52    }
53
54    /**
55     * Set a value
56     *
57     * @param  array $values
58     * @return Insert
59     */
60    public function values(array $values): Insert
61    {
62        $this->setValues($values);
63        return $this;
64    }
65
66    /**
67     * Set what to do on a insert conflict (UPSERT - PostgreSQL & SQLite)
68     *
69     * @param  array   $columns
70     * @param  ?string $key
71     * @return Insert
72     */
73    public function onConflict(array $columns, ?string $key = null): Insert
74    {
75        $this->conflictColumns = $columns;
76        $this->conflictKey     = $key;
77        return $this;
78    }
79
80    /**
81     * Set columns to handle duplicates/conflicts (UPSERT - MySQL-ism)
82     *
83     * @param  array $columns
84     * @return Insert
85     */
86    public function onDuplicateKeyUpdate(array $columns): Insert
87    {
88        $this->onConflict($columns);
89        return $this;
90    }
91
92    /**
93     * Render the INSERT statement
94     *
95     * @return string
96     */
97    public function render(): string
98    {
99        // Start building the INSERT statement
100        $sql     = 'INSERT INTO ' . $this->quoteId($this->table) . ' ';
101        $columns = [];
102        $values  = [];
103
104        $paramCount = 1;
105        $dbType     = $this->getDbType();
106
107        foreach ($this->values as $column => $value) {
108            $colValue = (strpos($column, '.') !== false) ?
109                substr($column, (strpos($column, '.') + 1)) : $column;
110
111            $columns[] = $this->quoteId($column);
112            $values[]  = ($value === null) ? 'NULL' : $this->quote($value);
113        }
114
115        $sql .= '(' . implode(', ', $columns) . ') VALUES (' . implode(', ', $values) . ')';
116
117        // Handle conflicts/duplicates (UPSERT)
118        if (!empty($this->conflictColumns)) {
119            $updates = [];
120            switch ($dbType) {
121                case self::MYSQL:
122                    foreach ($this->conflictColumns as $conflictColumn) {
123                        $updates[] = $this->quoteId($conflictColumn) . ' = VALUES(' . $conflictColumn .')';
124                    }
125                    $sql .= ' ON DUPLICATE KEY UPDATE ' . implode(', ', $updates);
126                    break;
127                case self::SQLITE:
128                case self::PGSQL:
129                    foreach ($this->conflictColumns as $conflictColumn) {
130                        $updates[] = $this->quoteId($conflictColumn) . ' = excluded.' . $conflictColumn;
131                    }
132                    $sql .= ' ON CONFLICT (' . $this->quoteId($this->conflictKey) . ') DO UPDATE SET '
133                        . implode(', ', $updates);
134                    break;
135            }
136        }
137
138        return $sql;
139    }
140
141    /**
142     * Render the INSERT statement
143     *
144     * @return string
145     */
146    public function __toString(): string
147    {
148        return $this->render();
149    }
150
151}